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#

FieldTypeRequiredDescription
urlsstring[]One of the twoUp to 50 links. With GET, a comma-separated list
urlstringOne of the twoOne 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#

JSON
{  "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 out

Stuck? Write to [email protected], or check live status.