Reference
Limits and errors
Every error is JSON with a short code you can switch on and one sentence for people. Limits are per account unless the table says per app.
Limits
| Limit | Value |
|---|---|
| apps | 5 |
| rows per app | 100,000 |
| database size per app | 500 MB |
| storage per account | 1 GB |
| file size | 50 MB (a bucket can lower it) |
| requests per minute | 1,200 across the account's apps |
| JSON body | 1 MB, 1,000 rows per insert |
| import body | 20 MB |
| restore body | 200 MB |
| functions per app | 20, source 1 MB |
| function timeout | default 10 s, max 30 s |
| function heap | default 128 MB, max 256 MB |
| concurrent function runs per app | 4 |
| webhooks per app | 20 |
| SQL | 10 s default, 30 s max, 1,000 rows returned |
| live streams per app | 100 |
| request logs kept | 7 days |
| end users per app | 10,000 |
| login codes | 1 per minute per email, 10 minutes valid, 5 attempts |
See where an app stands:
berth apps usage "$APP"
curl -sS https://api.atberth.com/v1/apps/$APP/usage \
-H "Authorization: Bearer $SECRET_KEY"Going over a limit answers 403 quota_exceeded. Nothing is deleted.
Rate limit headers
Authenticated responses carry X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset. Past the limit you get 429 rate_limited with Retry-After in seconds.
curl -sS -o /dev/null -D - https://api.atberth.com/v1/apps/$APP \
-H "Authorization: Bearer $SECRET_KEY" | grep -i -E '^(x-ratelimit|x-request-id)'Error format
curl -sS https://api.atberth.com/v1/apps/$APP/tables/missing/rows \
-H "Authorization: Bearer $SECRET_KEY"{"error": "not_found", "message": "One sentence.", "request_id": "req_..."}Every response, success or not, has an X-Request-Id header. Include it when you report a problem. berth logs --app "$APP" shows recent requests with their ids and statuses.
berth logs --app "$APP" --limit 5Error codes
| HTTP | error | Meaning |
|---|---|---|
| 400 | invalid_body | body missing, not JSON, or wrong shape |
| 400 | invalid_name | name breaks the naming rule |
| 400 | invalid_type | unknown column type |
| 400 | invalid_filter | bad filter, order, select, or cursor |
| 400 | invalid_value | a value could not be stored in its column |
| 400 | not_null_violation | a required column was missing |
| 400 | check_violation | a check constraint failed |
| 400 | sql_error | the SQL statement failed (message from Postgres) |
| 401 | unauthorized | missing, unknown, revoked, or expired credential |
| 403 | forbidden | the credential is valid but cannot do this |
| 403 | policy_violation | a table or bucket policy blocked the request |
| 403 | read_only | a write was sent in read only SQL mode |
| 403 | quota_exceeded | an account limit (apps, rows, storage, database size) was reached |
| 403 | origin_not_allowed | browser origin not in the app's cors_origins |
| 404 | not_found | no such route or resource |
| 405 | method_not_allowed | the path exists but not with this method |
| 409 | conflict | the name already exists |
| 409 | unique_violation | a unique constraint failed |
| 409 | foreign_key_violation | a referenced row is missing or still referenced |
| 409 | idempotency_conflict | same Idempotency-Key with a different request |
| 413 | payload_too_large | body over the limit for that endpoint |
| 415 | unsupported_media_type | wrong Content-Type |
| 429 | rate_limited | requests per minute exceeded. See Retry-After. |
| 500 | server_error | bug on our side, includes request_id |
| 502 | function_error | the function crashed or returned nothing |
| 503 | unavailable | temporarily overloaded |
| 504 | timeout | a query or function ran past its time limit |
What to retry
- Retry 429, 503, and network failures, with backoff. Honor
Retry-After. - Send an
Idempotency-Keyon writes you might retry, so a retry never inserts twice. See Queries. - Do not retry 4xx errors other than 429 without changing the request.