> Searlo API Docs — Link Resolver
> Source: https://searlo.tech/docs#search-goto
> Full docs: https://searlo.tech/docs/full.md

# 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](#search-web) 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](#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

```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](#async-tasks)) and as the Cloro-compatible `POST /monitor/google/goto`, at the same price and under the same limit.

## Code Examples

### cURL

```bash
# At or after the web answer's gotoResolveAfter
curl -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 GET
curl -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"
```

### JavaScript

```javascript
// Swap the wrapped links in a /search/web answer for their destinations
async 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;
  });
}
```

### Python

```python
import os
import time
from 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
```


---
*From https://searlo.tech/docs — see full docs at https://searlo.tech/docs/full.md*