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

LimitValue
apps5
rows per app100,000
database size per app500 MB
storage per account1 GB
file size50 MB (a bucket can lower it)
requests per minute1,200 across the account's apps
JSON body1 MB, 1,000 rows per insert
import body20 MB
restore body200 MB
functions per app20, source 1 MB
function timeoutdefault 10 s, max 30 s
function heapdefault 128 MB, max 256 MB
concurrent function runs per app4
webhooks per app20
SQL10 s default, 30 s max, 1,000 rows returned
live streams per app100
request logs kept7 days
end users per app10,000
login codes1 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 5

Error codes

HTTPerrorMeaning
400invalid_bodybody missing, not JSON, or wrong shape
400invalid_namename breaks the naming rule
400invalid_typeunknown column type
400invalid_filterbad filter, order, select, or cursor
400invalid_valuea value could not be stored in its column
400not_null_violationa required column was missing
400check_violationa check constraint failed
400sql_errorthe SQL statement failed (message from Postgres)
401unauthorizedmissing, unknown, revoked, or expired credential
403forbiddenthe credential is valid but cannot do this
403policy_violationa table or bucket policy blocked the request
403read_onlya write was sent in read only SQL mode
403quota_exceededan account limit (apps, rows, storage, database size) was reached
403origin_not_allowedbrowser origin not in the app's cors_origins
404not_foundno such route or resource
405method_not_allowedthe path exists but not with this method
409conflictthe name already exists
409unique_violationa unique constraint failed
409foreign_key_violationa referenced row is missing or still referenced
409idempotency_conflictsame Idempotency-Key with a different request
413payload_too_largebody over the limit for that endpoint
415unsupported_media_typewrong Content-Type
429rate_limitedrequests per minute exceeded. See Retry-After.
500server_errorbug on our side, includes request_id
502function_errorthe function crashed or returned nothing
503unavailabletemporarily overloaded
504timeouta 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-Key on writes you might retry, so a retry never inserts twice. See Queries.
  • Do not retry 4xx errors other than 429 without changing the request.