Errors and rate limits
The error format, what each status means, the per-plan request budget, and how to back off before you are refused.
The error format
Errors are RFC 9457 problem documents, served as application/problem+json:
{
"type": "https://api.sourceloop.ai/errors/insufficient-scope",
"title": "Insufficient scope",
"status": 403,
"detail": "This key cannot read deals.",
"remediation": "Add the deals:read scope to the key, or use a key that has it.",
"instance": "req_8f21c0a4"
}
Two fields are worth building against:
remediationsays what to do next, in a sentence. It is non-standard and deliberate: an error that explains itself costs one round trip, an opaque one costs three. It is also why an AI agent calling this API can usually correct itself without asking you.instanceis the request id, also returned in theSourceloop-Request-Idheader on every response. Quote it in support requests and we can find the exact call.
What each error means
type | Status | What happened |
|---|---|---|
invalid-request | 400 | A parameter is missing, malformed, or contradicts another |
unknown-metric | 400 | That metric does not exist. GET /v1/schema lists the ones that do |
unknown-dimension | 400 | Same, for a breakdown dimension |
unknown-filter-operator | 400 | The filter operator is not supported for that dimension |
invalid-window | 400 | The period could not be parsed, or from is after to |
window-too-large | 400 | The requested range exceeds what the endpoint will scan |
unsupported-combination | 400 | Each parameter is valid, but not together |
website-not-found | 404 | No website matches, or the key does not cover it |
insufficient-scope | 403 | The key is valid but lacks a scope. The body names it |
rate-limit-exceeded | 429 | Too many requests this minute. Wait Retry-After seconds |
Where a value was rejected against a fixed set, the problem document also carries an allowed array listing the valid values, so you rarely need to open this page.
Rate limits
Counted per workspace, not per key. A second key does not buy a second budget.
| Plan | Sustained requests per minute |
|---|---|
| Free | 60 |
| Pro | 120 |
| Business | 600 |
| Agency | 1,200 |
Every response carries the current budget, so a well-behaved client slows down before it is refused rather than after:
RateLimit-Limit: 600
RateLimit-Remaining: 574
RateLimit-Reset: 41
RateLimit-Reset is seconds until the window rolls over. Exceeding the limit returns 429 with Retry-After in seconds. Retry after that many seconds, not immediately, and not with a tight loop.
Analytics reads are additionally capped at four concurrent queries per workspace. One wide window beats many narrow ones: asking for 90 days once is both faster and cheaper for you than 90 requests for a day each.
Retrying safely
Retry 429 and 5xx. Do not retry 4xx other than 429, because the request itself is what needs changing.
Writes accept an Idempotency-Key header. Send the same key with a retried write and the original result is returned instead of the write happening twice, which matters when a network timeout leaves you unsure whether the first attempt landed.
curl -s -X POST "https://app.sourceloop.ai/api/v1/events" \
-H "Authorization: Bearer $SOURCELOOP_API_KEY" \
-H "Idempotency-Key: signup-8f21c0a4" \
-H "Content-Type: application/json" \
-d '{"events":[{"event_name":"signup_completed","email":"[email protected]"}]}'
Batch sends are partial-success: one malformed event does not discard the batch, and the response reports accepted and rejected counts separately.