# Errors

> Every error is `{ "error": { "code", "message" } }` with a matching HTTP status. Switch on `code`, never on the message. No error uses credits.

Base URL: `https://api.publicadsapi.com` · Auth: `authorization: Bearer <key>` · Web version: https://publicadsapi.com/docs/errors

cURL:

```bash
curl "https://api.publicadsapi.com/v1/google/ads?keywords=travel" \
  -H "authorization: Bearer $PADS_API_KEY"
```

Node.js:

```js
const res = await fetch('https://api.publicadsapi.com/v1/google/ads?keywords=travel', {
  headers: { authorization: `Bearer ${process.env.PADS_API_KEY}` },
});

const result = await res.json();
if (!res.ok) throw new Error(`${result.error.code}: ${result.error.message}`);
console.log(result.ads.length, 'ads; next page:', result.next_cursor);
```

Python:

```python
import os
import requests

res = requests.get(
    "https://api.publicadsapi.com/v1/google/ads?keywords=travel",
    headers={"authorization": f"Bearer {os.environ['PADS_API_KEY']}"},
    timeout=60,
)
result = res.json()
if not res.ok:
    raise RuntimeError(f"{result['error']['code']}: {result['error']['message']}")
print(len(result["ads"]), "ads; next page:", result["next_cursor"])
```

400 Bad Request:

```json
{
  "error": {
    "code": "INVALID_INPUT",
    "message": "keywords is not a parameter of GET /v1/google/ads. It takes: advertiser_id, domain, country, format, surface, political, date_from, date_to, cursor, include.",
    "field": "keywords"
  },
  "credits_used": 0,
  "credits_remaining": null
}
```

## Codes

| code | HTTP | When | What to do |
| --- | --- | --- | --- |
| INVALID_INPUT | 400 | A parameter missing, malformed or not taken by that platform. | Fix the parameter `field` names. Don't retry as it is. |
| INVALID_URL | 400 | A library URL we can't read. | Check the library address. Don't retry as it is. |
| UNAUTHENTICATED | 401 | No `Authorization: Bearer <key>` header. | Send `Authorization: Bearer <key>`. |
| INVALID_KEY | 401 | A key that is not ours, or was revoked. | Use a current key from [Keys](https://publicadsapi.com/app/keys). |
| QUOTA_EXCEEDED | 402 | No credits left this month for this call. | Upgrade at `upgrade_url`, add a card at `add_card_url`, or wait for the 1st. Agents: give your user `claim_url`. |
| PAYMENT_REQUIRED | 402 | A paid plan whose latest invoice is unpaid. | Pay the latest invoice at `upgrade_url`. |
| AD_NOT_FOUND | 404 | No ad with that id. | Check the id. The ad may have been taken down: don't retry. |
| ENDPOINT_NOT_FOUND | 404 | No endpoint at that path. | Check the path, e.g. `/v1/meta/ads`. |
| RATE_LIMITED | 429 | Over the key's requests a second. | Wait `Retry-After` seconds, then retry. |
| UPSTREAM_BLOCKED | 503 | The platform blocked every attempt, on several connections. | Wait `Retry-After` seconds (about a minute), then retry. |
| UPSTREAM_CHANGED | 502 | The platform's page changed and we couldn't read it. | Retry later. We're alerted and fix it; meanwhile other platforms work. |
| TIMEOUT | 504 | The platform didn't answer within 60 seconds. | Retry once; if it times out again, narrow the search. |
| BUSY | 503 | Every one of our browsers is busy. | Wait `Retry-After` seconds (a few), then retry. |
| INTERNAL | 500 | Our fault. | Retry once, then write to us with the `X-Request-Id`. |

## Retrying

- **Retry:** `RATE_LIMITED`, `BUSY` and `UPSTREAM_BLOCKED` after the `Retry-After` header's seconds; `TIMEOUT`, `UPSTREAM_CHANGED` and `INTERNAL` once, after a pause. Back off if they repeat.
- **Don't retry:** every other 4xx. The same request gets the same answer.
- **Nothing is charged** for any error, a block or a timeout included, so a retry costs only the call that succeeds.
- **Set a 60 second timeout** on your client. A live call can take 30 seconds or more when a platform blocks and we retry on new IPs; we answer `504 TIMEOUT` at 60.

## Other fields

| Field | On | What it is |
| --- | --- | --- |
| error.field | INVALID_INPUT | The parameter at fault, as you named it. |
| error.upgrade_url | QUOTA_EXCEEDED, PAYMENT_REQUIRED | Where a person changes plan or pays. |
| error.add_card_url | QUOTA_EXCEEDED on Free | Where to add a card and pay for each extra credit. |
| error.claim_url | QUOTA_EXCEEDED for an agent | The link an AI agent gives its user to claim the account. |
| credits_used, credits_remaining | Search and one ad | `0` used, and what is left this month. |

## Request ids

Every response to `/v1` carries an `X-Request-Id` header. Put it in a message to support and we can find the call.

See also: [The Ad record](https://publicadsapi.com/docs/api/ad-record.md) · [Credits and billing](https://publicadsapi.com/docs/credits.md)
