Errors
Stable, machine-readable error codes with a self-serve fix walkthrough for every one.
TL;DR
Every error returns a JSON envelope with a stable code field. Switch on code, not on the HTTP status — the status can change between releases, the code cannot.
The error envelope
Every non-2xx response from api.repull.dev follows the same shape. Optional fields are only set when relevant.
{
"error": {
"code": "invalid_params",
"message": "limit must be between 1 and 100",
"docs_url": "https://repull.dev/docs/errors/invalid_params",
"example": "curl https://api.repull.dev/v1/listings?limit=50 ..."
}
}code— stable identifier you can switch on. Listed in the table below.message— human-readable explanation. Includes the offending field name when relevant.docs_url— direct link to the per-code page below. Surface this in your UI for fast self-serve resolution.example— a corrected example you can copy-paste. Set on a subset of codes today; expanding over time.
Reference
Click any code for a fix walkthrough with curl + TypeScript examples and a section for AI agents.
| Status | Code | Description |
|---|---|---|
| 400 | invalid_action | The `action` field is not one of the values this endpoint accepts. |
| 400 | invalid_body | A field in the request body failed validation. |
| 400 | invalid_param | A single path parameter (typically an id) failed validation. |
| 400 | invalid_params | A query string parameter failed validation. |
| 400 | invalid_provider | The provider in the URL or session does not match the request. |
| 400 | invalid_type | The `type` field is not one of the values this endpoint accepts. |
| 400 | unknown_params | The request included a parameter that is not in the allowlist for this endpoint. |
| 401 | invalid_credentials | The credentials supplied to a Connect call were rejected by the upstream channel. |
| 401 | unauthorized | Missing or invalid API key. |
| 402 | listings_limit_exceeded | The workspace has more active listings than its plan allows. DELETE listings or upgrade to clear. |
| 403 | forbidden | Authenticated, but the workspace does not have access to this resource or feature. |
| 404 | not_found | The id does not exist, or it belongs to a different workspace. |
| 409 | invalid_state | The resource is not in a state that allows this operation. |
| 409 | no_connection | No active channel connection for the requested provider in this workspace. |
| 409 | provider_unavailable | The provider is in the catalog but not yet available for new connections. |
| 409 | session_terminal | The Connect session is already completed, errored, or cancelled. |
| 410 | session_expired | The Connect session lifetime has elapsed. Start a new session. |
| 429 | rate_limited | Too many requests. Back off and retry using the Retry-After header. |
| 500 | internal_error | Something went wrong on our side. Safe to retry with backoff. |
| 501 | not_implemented | The endpoint exists but the requested capability is not yet available. |
How to handle errors
A typical error handler covers four buckets. Per-code pages give you the specifics.
import { Repull } from '@repull/sdk'
const repull = new Repull({ apiKey: process.env.REPULL_KEY! })
async function safeCall<T>(fn: () => Promise<T>): Promise<T | null> {
try {
return await fn()
} catch (err: any) {
switch (err.code) {
// 1. Bad input — fix the request, do not retry verbatim
case 'invalid_params':
case 'invalid_body':
case 'invalid_param':
case 'invalid_action':
case 'invalid_type':
case 'unknown_params':
case 'invalid_provider':
console.error('Bad request:', err.message)
return null
// 2. Auth / permissions — surface to operator, do not retry
case 'unauthorized':
case 'forbidden':
case 'invalid_credentials':
console.error('Auth failure:', err.message)
return null
// 3. Resource state — re-fetch and decide
case 'not_found':
case 'invalid_state':
case 'no_connection':
case 'session_expired':
case 'session_terminal':
case 'provider_unavailable':
return null
// 4. Transient — back off and retry
case 'rate_limited':
case 'internal_error':
await new Promise(r => setTimeout(r, 1000))
return await fn()
default:
throw err
}
}
}For AI agents
Every code page ends with an If you're an AI agent block — a 1-2 sentence instruction for what to change in the next call. The docs_url field in the envelope deep-links straight to it. See Using Repull from AI agents for the full integration guide.