Income Graph Developers

Errors and rate limits

The error format, every error code, and how rate limits and daily quotas work.

Error format

Every non-2xx response has the same JSON shape and an X-Request-Id header with the same id:

{
  "error": {
    "code": "invalid_request",
    "message": "The request did not match the API specification.",
    "requestId": "5b9f1f4e-7a53-4e1a-b8a2-7d5b8c1a9f10",
    "details": [{ "location": "query.limit", "message": "must be <= 100" }]
  }
}

Branch on error.code. It is stable. message is for people and may change. details is present for invalid_request only, and current for conflict only. Quote the request id when you contact support.

Error codes

StatuscodeMeaningWhat to do
400invalid_requestA parameter or body failed validation.Fix the request using details.
401missing_api_keyNo Bearer key was sent.Send Authorization: Bearer <key>.
401invalid_api_keyThe key is unknown, malformed or revoked.Create a new key.
403subscription_requiredThe owner has no active subscription.Subscribe on the pricing page.
403subscription_past_dueThe last payment failed.Fix billing on the account page.
403insufficient_scopeA write with a read-only key.Create a read-write key on the account page.
404not_foundNo such graph on this account (also: it was already deleted).Check the id with GET /v1/graphs.
409conflictThe graph changed since you read it. Nothing was written or deleted. error.current is its current version.Read the graph again, re-apply your change to what is there now, and retry with the new updatedAt.
409graph_limit_reachedThe account already holds 100 graphs.Delete one before creating another.
413payload_too_largeThe body is over 300 KB.Send fewer sources.
422idempotency_key_reusedThe Idempotency-Key was used for a different request, or for a graph since deleted.Use a new key for a new graph.
429rate_limitedToo many requests this minute.Wait Retry-After seconds.
429quota_exceededThe daily quota is used up.Wait until 00:00 UTC.
500internal_errorSomething failed on our side.Retry with backoff. Reads are always safe to retry; see Writing safely for writes.

Rate limits and quotas

Limits apply per API key:

LimitValueWindow
Rate limit60 requestsany one minute (fixed windows that start on the minute)
Write limit20 writesany one minute; create, update and delete count here and against the rate limit
Daily quota5,000 requestsone UTC day

Requests rejected for rate limiting do not count against the daily quota. Failed authentication does not count at all.

Every response from an authorized key carries:

HeaderMeaning
X-RateLimit-LimitRequests allowed per minute.
X-RateLimit-RemainingRequests left in the current minute.
X-RateLimit-ResetSeconds until the minute window ends.

A 429 also carries Retry-After (seconds). A minimal retry loop:

import time, requests

def get(url, headers):
    for attempt in range(5):
        r = requests.get(url, headers=headers, timeout=10)
        if r.status_code != 429:
            return r
        time.sleep(int(r.headers.get("Retry-After", "5")))
    raise RuntimeError("still rate limited")

Need more? Tell us what you are building and we will raise your limits.

Writing safely

Writes need a key with the read_write scope and an active subscription. Three rules keep an agent or script from doing damage:

An account holds at most 100 graphs through the API. Source trees are validated the way the web app validates them: source and fee ids are unique numeric strings, a group’s children and its members’ parentId must agree, and amounts cannot be negative. A problem is reported as invalid_request with a details entry such as body.sources[1].parentId.