# Errors, limits and paging

What every response of the REST API has in common: one error shape, a rate limit with its headers, the same paging on every list, and a request ID.

## Errors

A failed request answers an HTTP error status and this body:

```json
{
  "error": {
    "type": "not_found",
    "code": "trend_not_found",
    "message": "Trend not found."
  }
}
```

- `type` is the stable family. Branch on it.
- `code` is the precise reason. New codes may appear; treat an unknown one by its `type`.
- `message` is for people and logs. Its wording may change.
- `details` is present on some errors, such as invalid parameters.

| `type` | Status | When |
|---|---|---|
| `invalid_request` | 400 | A parameter is missing, malformed or out of range. |
| `authentication` | 401 | No key, or an unknown or revoked key. |
| `permission` | 403 | The key lacks the scope, or the account lacks API access. |
| `not_found` | 404 | No such trend, or the feed, article, folder or monitor is not the account's. Also an unknown path. |
| `rate_limit` | 429 | Over the per-minute limit, or the month's quota is spent. |
| `server` | 500 | Something failed on EarlyWave's side. |

### Codes you will meet

| `code` | `type` |
|---|---|
| `invalid_parameters` | `invalid_request` |
| `invalid_category`, `invalid_stage`, `invalid_reach` | `invalid_request` |
| `invalid_country`, `invalid_publisher_country` | `invalid_request` |
| `missing_api_key`, `invalid_api_key` | `authentication` |
| `missing_scope`, `feature_not_available`, `account_inactive` | `permission` |
| `trend_not_found`, `feed_not_found`, `article_not_found`, `category_not_found`, `monitor_not_found` | `not_found` |
| `route_not_found` | `not_found` |
| `rate_limit_exceeded` | `rate_limit` |
| `quota_exceeded` | `rate_limit` |
| `internal_error`, `auth_unavailable` | `server` |

### Invalid parameters

Parameters are checked before anything runs. `details` lists every problem, one per parameter:

```bash
curl "https://api.earlywave.ai/api/v1/trends?limit=500&sort=popular" \
  -H "Authorization: Bearer $EARLYWAVE_API_KEY"
```

```json
{
  "error": {
    "type": "invalid_request",
    "code": "invalid_parameters",
    "message": "sort: Invalid option: expected one of \"signal\"|\"recent\"|\"sources\"; limit: Too big: expected number to be <=100",
    "details": [
      { "param": "sort", "message": "sort: Invalid option: expected one of \"signal\"|\"recent\"|\"sources\"" },
      { "param": "limit", "message": "limit: Too big: expected number to be <=100" }
    ]
  }
}
```

A few rules make parameters forgiving:

- An empty value (`?category=`) counts as absent.
- Unknown parameters are ignored.
- Numbers may be sent as strings (`limit=5`); they are converted.
- Comma-separated lists (`category=politics,economy`) are checked value by value: an unknown value answers `invalid_category` and lists the allowed ones.

## Rate limit and monthly quota

Each plan sets two counters, shared by all the account's keys and by REST and MCP together: one REST call or one MCP tool call counts as one request.

| Plan | Per minute | Per calendar month |
|---|---|---|
| Pro | 60 requests | 10,000 requests |
| Max | 300 requests | 50,000 requests |

Minute windows are fixed: each one is a calendar minute, and the counter resets at the start of the next minute. The monthly quota resets on the 1st at 00:00 UTC. A refused request counts against neither. Every authenticated response carries:

| Header | Meaning |
|---|---|
| `X-RateLimit-Limit` | Requests allowed per minute. |
| `X-RateLimit-Remaining` | Requests left in the current minute. |
| `X-RateLimit-Reset` | Unix time, in seconds, when the minute window resets. |
| `X-Quota-Limit` | Requests allowed this calendar month (`-1` on a contract without a monthly limit). |
| `X-Quota-Remaining` | Requests left this month. |
| `X-Quota-Reset` | Unix time, in seconds, of the 1st of next month. |

Over either limit, the API answers `429` with `error.type = "rate_limit"` and a `Retry-After` header in seconds. The code says which counter refused: `rate_limit_exceeded` for the minute, `quota_exceeded` for the month.

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1792400460
X-Quota-Limit: 10000
X-Quota-Remaining: 8211
X-Quota-Reset: 1793577600
```

Wait `Retry-After` seconds, then retry; a spent month waits for the 1st, or a higher plan. On MCP, a tool call over a limit returns a tool error that names the counter; the assistant can retry after the minute ends.

## Paging

Every list takes the same two parameters:

| Parameter | Default | Range |
|---|---|---|
| `limit` | 20 | 1–100 |
| `offset` | 0 | 0 and up |

and answers `limit`, `offset` and `has_more`. Most lists also answer `total`, the number of matching results.

```json
{ "trends": [ … ], "total": 37, "limit": 20, "offset": 0, "has_more": true }
```

To read everything, call again with `offset + limit` while `has_more` is `true`:

```js
let offset = 0;
const all = [];
for (;;) {
  const res = await fetch(`https://api.earlywave.ai/api/v1/trends?country=FR&limit=100&offset=${offset}`, {
    headers: { Authorization: `Bearer ${process.env.EARLYWAVE_API_KEY}` },
  });
  const page = await res.json();
  all.push(...page.trends);
  if (!page.has_more) break;
  offset += page.limit;
}
```

A few operations differ:

- `GET /monitors` and `GET /categories` return everything at once, with `total`.
- `GET /monitors/{monitor_id}/results` takes `limit` but no `offset`. Narrow the period (`days`) or the text (`query`) instead.
- `GET /discover/keywords` takes `limit` only.

Results can move between two calls: a trend gains articles and its rank changes. When you need one complete pass, narrow the filters so that a page of 100 holds everything, or page right away.

## Request ID

Every response, success or error, carries an `X-Request-Id` header. Quote it when you report a problem: it points to that exact request in EarlyWave's logs.

## Retrying

| Status | Retry? |
|---|---|
| 400, 401, 403, 404 | No. Fix the request, the key or the plan first. |
| 429 | Yes, after `Retry-After` seconds. |
| 500 | Yes, with a growing delay (1 s, 2 s, 4 s…), a few times at most. |

Every operation is a read, so retrying a request never changes anything.

---
Source: https://www.earlywave.ai/developers/docs/errors-and-limits · All docs: https://www.earlywave.ai/llms.txt
