Search
Link Resolver
Resolve Google redirect links (https://www.google.com/goto?url=…) to the pages they point to, such as the wrapped links in a Web Search answer.
Endpoint#
- POST
/search/goto - GET
/search/goto
Credits: 0.05 per resolved link. Links that do not resolve, and failed calls, are free. An account that has never bought credits can send 30 links a minute; past it, 429 4208 (Rate Limiting).
Parameters#
| Field | Type | Required | Description |
|---|---|---|---|
urls | string[] | One of the two | Up to 50 links. With GET, a comma-separated list |
url | string | One of the two | One link |
POST takes them in a JSON body, GET in the query string. Only Google redirect links (google.<tld>/goto?url=…) are followed; anything else comes back invalid. Ask at or after the web answer's gotoResolveAfter: Google refuses a fresh link for about 20 to 35 seconds after its results page is fetched, and a link asked for too early comes back unresolved, and is not charged.
Response#
{ "success": true, "resolved": [ { "input": "https://www.google.com/goto?url=…", "url": "https://learn.example.org/ml-course", "resolved": true, "status": "ok", "domain": "learn.example.org" }, { "input": "https://www.google.com/goto?url=…", "url": null, "resolved": false, "status": "no_redirect", "domain": "" } ], "total": 2, "ok": 1, "credits": 0.05}resolved has one row per link, in the order sent. status is ok (url is the destination), no_redirect (Google did not redirect it), invalid (not a Google redirect link) or error (the lookup failed). A row that did not resolve has url: null and an empty domain. credits and the X-Credits-Deducted header say what the call cost.
The same resolver runs as async goto tasks (Async Tasks) and as the Cloro-compatible POST /monitor/google/goto, at the same price and under the same limit.
Examples
# At or after the web answer's gotoResolveAftercurl -X POST "https://api.searlo.tech/api/v1/search/goto" \ -H "x-api-key: sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{ "urls": ["https://www.google.com/goto?url=…"] }' # One link with GETcurl -G "https://api.searlo.tech/api/v1/search/goto" \ --data-urlencode "url=https://www.google.com/goto?url=…" \ -H "x-api-key: sk_your_api_key"// Swap the wrapped links in a /search/web answer for their destinationsasync function resolveWrapped(answer) { const wrapped = answer.organic.filter((r) => r.resolved === false); if (!wrapped.length) return answer.organic; // Google refuses fresh wrappers: wait until gotoResolveAfter const wait = Date.parse(answer.gotoResolveAfter) - Date.now(); if (wait > 0) await new Promise((done) => setTimeout(done, wait)); const res = await fetch("https://api.searlo.tech/api/v1/search/goto", { method: "POST", headers: { "x-api-key": process.env.SEARLO_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ urls: wrapped.map((r) => r.redirectUrl) }), }); const rows = new Map((await res.json()).resolved.map((row) => [row.input, row])); return answer.organic.map((r) => { const row = r.resolved === false ? rows.get(r.redirectUrl) : undefined; return row && row.resolved ? { ...r, link: row.url, domain: row.domain } : r; });}import osimport timefrom datetime import datetime import requests def resolve_wrapped(answer): """Swap the wrapped links in a /search/web answer for their destinations.""" wrapped = [r for r in answer["organic"] if r.get("resolved") is False] if not wrapped: return answer["organic"] # Google refuses fresh wrappers: wait until gotoResolveAfter after = datetime.fromisoformat(answer["gotoResolveAfter"].replace("Z", "+00:00")) time.sleep(max(0.0, after.timestamp() - time.time())) res = requests.post( "https://api.searlo.tech/api/v1/search/goto", json={"urls": [r["redirectUrl"] for r in wrapped]}, headers={"x-api-key": os.environ["SEARLO_API_KEY"]}, ) rows = {row["input"]: row for row in res.json()["resolved"]} out = [] for r in answer["organic"]: row = rows.get(r.get("redirectUrl")) if r.get("resolved") is False else None out.append({**r, "link": row["url"], "domain": row["domain"]} if row and row["resolved"] else r) return outStuck? Write to [email protected], or check live status.