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.

StatusCodeDescription
400invalid_actionThe `action` field is not one of the values this endpoint accepts.
400invalid_bodyA field in the request body failed validation.
400invalid_paramA single path parameter (typically an id) failed validation.
400invalid_paramsA query string parameter failed validation.
400invalid_providerThe provider in the URL or session does not match the request.
400invalid_typeThe `type` field is not one of the values this endpoint accepts.
400unknown_paramsThe request included a parameter that is not in the allowlist for this endpoint.
401invalid_credentialsThe credentials supplied to a Connect call were rejected by the upstream channel.
401unauthorizedMissing or invalid API key.
402listings_limit_exceededThe workspace has more active listings than its plan allows. DELETE listings or upgrade to clear.
403forbiddenAuthenticated, but the workspace does not have access to this resource or feature.
404not_foundThe id does not exist, or it belongs to a different workspace.
409invalid_stateThe resource is not in a state that allows this operation.
409no_connectionNo active channel connection for the requested provider in this workspace.
409provider_unavailableThe provider is in the catalog but not yet available for new connections.
409session_terminalThe Connect session is already completed, errored, or cancelled.
410session_expiredThe Connect session lifetime has elapsed. Start a new session.
429rate_limitedToo many requests. Back off and retry using the Retry-After header.
500internal_errorSomething went wrong on our side. Safe to retry with backoff.
501not_implementedThe 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.

AI