Pagination & errors

One error envelope everywhere, cursor pagination on the transaction feed, and predictable rate limits.

The error envelope

Every error response, on any endpoint and any status code (400, 401, 403, 404, 405, 429), has the same shape:

json
{
  "error": {
    "code": "not_found",
    "message": "Not found."
  }
}

Branch on error.code, which is a stable machine-readable string, rather than parsing error.message, which is human-facing and may be reworded.

Pagination

Transactions (cursor)

The transaction feed is cursor-paginated because bank feeds are insertion-heavy: new rows land at the top on every sync. A cursor keeps your walk stable where page numbers would skip or repeat rows. Each page returns up to 100 transactions, newest first:

json
{
  "next": "https://api.endute.com/v1/accounts/6f2b.../transactions?cursor=cD0yMDI2LTA2LTI4",
  "previous": null,
  "results": [ /* up to 100 transactions, newest first */ ]
}
  • Follow the next URL to get the next page. When next is null you have reached the end.
  • previous walks back toward the newest rows.
  • The page size is fixed; there is no page-size parameter.

Treat the id as your idempotency key

A cursor guarantees you never skip a pre-existing row while walking the feed. Because a new transaction sharing a page-boundary date can re-serve that boundary row once, de-duplicate on the transaction id as you ingest.

Investment activities (capped list)

Investment activities are not cursor-paginated: some rows (such as cash dividends) have no trade date, so they have no stable cursor position. Instead the list is returned in full up to a hard cap of the newest 500 rows, with a truncated flag:

json
{
  "truncated": false,
  "results": [ /* investment activities, newest first */ ]
}

When truncated is true, narrow the window with the from and to date parameters to see older rows.

Rate limits

These limits apply across the API:

ScopeLimitNotes
Per API key120 requests / minuteBurst control on a single key.
Per developer account50,000 requests / dayFair-use ceiling across all of your keys.
Unauthenticated30 requests / minute per IPBackstop for requests without a valid key.

When you exceed a limit you get a 429 with a Retry-After header (seconds to wait) and the error envelope with code throttled:

http
HTTP/1.1 429 Too Many Requests
Retry-After: 42

{
  "error": {
    "code": "throttled",
    "message": "Request was throttled. Expected available in 42 seconds."
  }
}

Respect Retry-After and back off rather than retrying immediately.

Paths and methods

  • Paths have no trailing slash. Use /v1/accounts, not /v1/accounts/. A mistyped path returns the same error envelope as any other failure, not a redirect.
  • The API is read-only. Only GET (and HEAD) are supported. Any other method, including OPTIONS, returns 405 Method Not Allowed in the error envelope.
  • Ids in paths are validated by type; a malformed id is a 404, and another account's id is also a 404 (never a 403, which would confirm it exists).