Handle API responses
ROIC API v3 uses consistent resource, list, and error shapes. Learn these shapes once, then use them across every endpoint. This guide also covers pagination, request limits, and retries.
Read a resource response
A successful single-resource request returns the resource directly. It isn't
wrapped in a data field.
{
"id": "tkr_cO46h8pgSu9Qqy",
"object": "v3.reference.ticker",
"symbol": "NASDAQ:AAPL",
"ticker": "AAPL"
}Every resource contains a stable public id, a constant object type, and
resource-specific fields. Nullable fields remain present as null when a
value is unavailable.
Read a list response
A successful list request returns a data array and URLs for moving through
the collection.
{
"data": [],
"next_page_url": "/v3.0.0/tickers?limit=50&page=page_v1_opaque_token",
"previous_page_url": null
}The first page has previous_page_url: null. The final page has
next_page_url: null.
Follow pagination URLs
Most list endpoints use cursor pagination. Request the first page without a
page parameter, then follow the returned URL.
curl "https://api.roic.ai/v3.0.0/tickers?limit=50" \
-H "Authorization: Bearer $ROIC_API_KEY"Bearer-authenticated clients can follow next_page_url unchanged while
continuing to send the same Authorization header.
curl "https://api.roic.ai/v3.0.0/tickers?limit=50&page=page_v1_opaque_token" \
-H "Authorization: Bearer $ROIC_API_KEY"Query-authenticated clients must append apikey again because returned page
URLs never contain credentials. Page-size defaults and maximums vary by
endpoint; each endpoint page documents its limit parameter.
Treat page tokens as opaque
Don't decode, edit, or create page tokens. Each token is bound to the endpoint
path, filters, order, resource family, and limit that created it.
Start a new request without page after changing a filter, sort order, or page
size. Tokens expire after one hour. Modified, expired, or mismatched tokens
return 400 Bad Request in the standard error envelope.
The API uses stable keyset ordering, but data can change between requests. Store the returned resources if you need a reproducible dataset.
Recognize unpaginated lists
The metric-definition list returns every matching definition in one response.
It doesn't accept limit, page, or order, and both page URLs are always
null.
Read a binary response
GET /v3.0.0/company/logo/{identifier} is the only binary exception. It returns
raw WebP or PNG bytes with Content-Type, ETag, and Cache-Control headers.
A conditional request can return 304 Not Modified with an empty body.
Logo failures still return the standard JSON error envelope.
Handle API errors
Every failure returns an error object with stable fields your client can
inspect.
{
"error": {
"type": "invalid_request_error",
"code": "resource_missing",
"message": "The requested resource does not exist.",
"request_id": "req_0af7651916cd43dd8448eb211c80319c"
}
}Branch on type and code, not the human-readable message. The optional
param field identifies a request value that you can change.
Use HTTP status codes
HTTP status communicates the broad failure category. The error type and
code provide stable programmatic detail.
| Status | Type | Meaning |
|---|---|---|
400 | invalid_request_error | A parameter, body, or page token is invalid. |
401 | authentication_error | Credentials are missing, conflicting, malformed, or unknown. |
402 | payment_required_error | The authenticated caller's plan doesn't include access. |
404 | invalid_request_error | A resource or v3 route doesn't exist. |
409 | conflict_error | The request conflicts with existing state. |
429 | rate_limit_error | The caller exceeded a request limit. |
500 | api_error | An unexpected server failure occurred. |
503 | api_error | A required dependency is unavailable. |
Handle rate limits
Use rate-limit headers to monitor your request quota and Retry-After to
recover from 429 Too Many Requests responses.
Understand request limits
Each plan provides a request quota with a per-minute limit and an available history range. v2 and v3 draw from the same quota, so requests to either version reduce the same remaining count.
| Plan | Requests / minute | History |
|---|---|---|
| Free | 5 | 2 years |
| Individual | 300 | 5 years |
| Professional | Unlimited | All available |
| Enterprise | Unlimited | All available |
The API uses a sliding 60-second window per API key. An authenticated request counts before parameter validation, so invalid parameters still consume one request when the limit hasn't been reached. See pricing for current plan details.
Read rate-limit headers
Metered responses normally include three headers. Read them after each request instead of estimating usage in your client.
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Enforced request limit for the current window. |
X-RateLimit-Remaining | Requests remaining after the current request. |
X-RateLimit-Reset | Unix timestamp when the oldest request leaves the window. |
Treat these headers as optional because they can be absent from a response.
Recover from a request limit
When you reach the limit, the API returns 429 Too Many Requests, a
Retry-After header measured in seconds, and the standard error envelope.
{
"error": {
"type": "rate_limit_error",
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded for the free plan. Retry in 42 seconds or update your plan.",
"request_id": "req_0af7651916cd43dd8448eb211c80319c"
}
}Stop sending requests for the duration of Retry-After. If another 429
follows, use capped exponential backoff with jitter. If normal traffic
regularly reaches the limit, choose a plan with a larger request quota.
Trace a request
Every success and failure includes a Request-Id response header. Error bodies
repeat the same value in error.request_id. Include this ID when you contact
support so the request can be matched to server traces.
Unexpected failures are redacted. Responses don't expose stack traces, database messages, credentials, or dependency payloads.
Retry failed requests
Choose a retry policy from the status and error code. Error responses use
Cache-Control: no-store and must not be cached.
- For
429, wait forRetry-Afteras described in recover from a request limit. - For transient
500and503responses, use capped exponential backoff with jitter. - For validation or authentication errors, change the request before retrying.
Next steps
Apply these response rules to the resource family you want to use.