# Errors

> How the grovs API reports errors: the error body, status codes, error codes and rate limits.

Source: https://docs.grovs.io/docs/api-reference/errors

## Errors

grovs uses HTTP status codes to say whether a request worked. A `2xx` means it did. Treat the status code as the source of truth.

Most errors have a JSON body with an `error` message written for people. Show it, but do not parse it. The wording can change.

Where a program needs to tell failures apart, the body also has an `error_code`. Branch on `error_code`, never on `error`.

**Response 429 Too Many Requests**

```json
{
  "error": "campaigns: 600 per minute",
  "error_code": "rate_limited"
}
```

**Response 404 Not Found**

```json
{
  "error": "Link not found"
}
```

## Status codes

| Status | Meaning                                                                                                                                                  |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200`  | Success.                                                                                                                                                 |
| `202`  | Accepted. The work continues in the background, such as a push send. Poll the resource for progress.                                                     |
| `400`  | The request is malformed: a missing `PROJECT-KEY`, an `ENVIRONMENT` other than `production` or `test`, invalid JSON, an unreadable date or a bad cursor. |
| `401`  | No scoped key, or it is unknown or revoked.                                                                                                              |
| `402`  | The event stream needs an active paid plan on grovs Cloud.                                                                                               |
| `403`  | The credential is valid but not allowed here: a key from another project or instance, or a plan without the feature.                                     |
| `404`  | The resource does not exist, belongs to another environment, or the feature is not available on this install.                                            |
| `409`  | The resource is in the wrong state for this call, such as deleting a launched in-app message, or it changed since you read it.                           |
| `422`  | Validation failed. `error` names the field or condition at fault.                                                                                        |
| `429`  | A per-feature limit was reached. Retry later. See the `error_code`.                                                                                      |
| `503`  | A dependency is unavailable, or you were rate limited. Back off and retry.                                                                               |
| `5xx`  | Any other server error. Retry with backoff. If it continues, contact <support@grovs.io>.                                                                 |

**Response 403 Forbidden**

```json
{
  "error": "Forbidden"
}
```

**Response 422 Unprocessable Entity**

```json
{
  "error": "Link not found"
}
```

## Error codes

Branch on these `error_code` values.

| `error_code`             | Status | Meaning                                                                                                                                                                                 |
| ------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clickhouse_unavailable` | `503`  | Analytics storage is off or unreachable. Retry later. On a self-hosted install, check ClickHouse.                                                                                       |
| `clickhouse_stale`       | `503`  | Analytics storage is up, but its rollups are being rebuilt. Retry later.                                                                                                                |
| `path_generation_failed` | `503`  | grovs could not allocate a link path. Retry.                                                                                                                                            |
| `rate_limited`           | `429`  | A per-minute limit is used up: the server push budget, or 120 requests per key on segments and in-app messages. Nothing was created. Retry after `Retry-After` seconds, or next minute. |
| `enterprise_required`    | `403`  | Server push, segments and in-app messages need an active paid plan on grovs Cloud.                                                                                                      |
| `segment_busy`           | `429`  | Too many segment queries are running. Retry in a few seconds.                                                                                                                           |
| `invalid_definition`     | `422`  | A saved segment cannot be evaluated: it uses `in_list` before a list was uploaded, or `purchase` without revenue tracking.                                                              |
| `window_expired`         | `422`  | A segment's "did not" condition reaches past your data retention.                                                                                                                       |
| `query_too_heavy`        | `422`  | A segment query exceeded its time or memory budget. Narrow a window or remove a condition.                                                                                              |
| `upload_in_progress`     | `409`  | Another identifier list upload for this segment is still running.                                                                                                                       |

**Response 503 Service Unavailable**

```json
{
  "error": "Analytics temporarily unavailable",
  "error_code": "clickhouse_unavailable"
}
```

## Rate limits

Over these per-minute limits, the answer is **`503`** with a plain-text body, not `429`. Back off and retry. Do not retry in a tight loop.

| What                   | Limit              |
| ---------------------- | ------------------ |
| Event stream pull feed | 120 per `est_` key |
| Audit log export       | 60 per `aet_` key  |

[Server push](https://docs.grovs.io/docs/api-reference/server-push) has its own per-project budget. Over it, the answer is `429` with `error_code: "rate_limited"` and a JSON body.

[Segments](https://docs.grovs.io/docs/api-reference/segments) and [in-app messages](https://docs.grovs.io/docs/api-reference/in-app-messages) share a limit of 120 requests a minute per project key. Over it, the answer is `429` with `error_code: "rate_limited"`, a `retry_after` field and a `Retry-After` header, both in seconds.

**Response 503 Service Unavailable**

```text
Server Error
```

**Response 429 Too Many Requests**

```json
{
  "error": "Rate limit exceeded; retry in 23s",
  "error_code": "rate_limited",
  "retry_after": 23
}
```
