Using Repull from AI agents

Patterns and primitives for LLMs, Cursor, Cline, Claude, and MCP-based agents calling Repull.

TL;DR

One Bearer token. Stable error code field with self-serve docs URLs in the envelope. Cursor pagination. Idempotency-Key on every retryable write. The whole API is described by api.repull.dev/openapi.json — point your tool at it and discover.

Discovery

The full API surface is published as a single OpenAPI 3.0 spec. Most agent frameworks (Claude tools, MCP, Cursor) can consume it directly to auto-generate tool definitions.

# Fetch the full spec (no auth required)
curl https://api.repull.dev/openapi.json
  • MCP server — a one-line install for Claude / Cline / Cursor — see MCP Server.
  • SDKs — typed clients in TS, Python, Go, Ruby, .NET, PHP. See SDKs.
  • CLI — for one-off shell calls inside agent loops. See CLI.

Authentication

Repull uses a single Bearer token — no OAuth dance, no token refresh, no per-call signing. Mint a key from /dashboard/keysand store it in the agent's secrets store.

curl https://api.repull.dev/v1/listings \
  -H "Authorization: Bearer sk_test_YOUR_KEY"

For shells where lowercased headers cause trouble (some legacy proxies, certain framework middlewares), x-api-key is accepted as an alternate header.

Error handling

Every error response is a JSON envelope with a stable code string and a docs_url that deep-links to a self-serve fix walkthrough. Build your handler around code, never around the HTTP status — the status can change between releases, the code cannot.

{
  "error": {
    "code": "invalid_params",
    "message": "limit must be between 1 and 100",
    "docs_url": "https://repull.dev/docs/errors/invalid_params"
  }
}

When you see error X, do this

CodeWhat to change in the next call
unauthorizedStop. The key is missing or invalid. Ask the user for a fresh key from /dashboard/keys.fix walkthrough →
forbiddenThe plan does not include this feature. Read err.message and surface the upgrade path; do not retry.fix walkthrough →
not_foundThe id does not resolve in this workspace. List the parent collection and pick a real id.fix walkthrough →
invalid_paramsA query string parameter was rejected. err.message names the parameter — fix that one and retry.fix walkthrough →
invalid_bodyA body field was rejected. err.message names the field — patch that one field and retry.fix walkthrough →
unknown_paramsYou sent a parameter that does not exist. Drop it (or rename to the canonical name) and retry.fix walkthrough →
rate_limitedWait Retry-After seconds. Pause the loop entirely until X-RateLimit-Reset.fix walkthrough →
internal_errorRepull-side failure. Wait 1s and retry; on second failure, exponential backoff up to 3 attempts.fix walkthrough →
no_connectionThe user has not connected this channel. Stop and offer them the Connect URL.fix walkthrough →
session_expiredMint a new Connect session — POST /v1/connect/{provider} — and redirect the user.fix walkthrough →

Full reference at /docs/errors. Every code page ends with an “If you're an AI agent” block.

Idempotency

Wrap every retryable write with an Idempotency-Key header. Repull dedupes by key for 24 hours, so a network hiccup that triggers a retry never produces a duplicate side-effect — critical when an agent retries because it does not know whether the first attempt landed.

curl -X POST https://api.repull.dev/v1/reservations \
  -H "Authorization: Bearer sk_test_YOUR_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"propertyId": "123", "checkIn": "2026-06-01", "checkOut": "2026-06-05"}'

Generate the key once when you decide to perform the operation, and reuse it across retries. Generating a fresh key per attempt defeats the purpose. Full guide: Idempotency Keys.

Pagination

List endpoints return a cursor in the response body. Pass it back as cursor=… on the next call.has_more tells you when to stop. limit caps at 100.

{
  "data": [/* 50 items */],
  "has_more": true,
  "next_cursor": "eyJpZCI6IjIxNjIwMSJ9"
}
async function listAll<T>(fetchPage: (cursor?: string) => Promise<{ data: T[]; has_more: boolean; next_cursor?: string }>) {
  const out: T[] = []
  let cursor: string | undefined = undefined
  do {
    const page = await fetchPage(cursor)
    out.push(...page.data)
    cursor = page.has_more ? page.next_cursor : undefined
  } while (cursor)
  return out
}

Common agent patterns

List-then-enrich

Fetch a paginated list to get ids, then enrich the records you actually need. Cheaper than fetching everything with expand=* when the agent only operates on a subset.

const { data: reservations } = await repull.reservations.list({ status: 'confirmed' })
const recent = reservations.slice(0, 10)
const enriched = await Promise.all(
  recent.map(r => repull.reservations.get(r.id, { expand: 'guest,listing' }))
)

Retry on 429 with backoff

Honour Retry-After. Add jitter to avoid thundering-herd retries when a whole agent fleet hits the cap simultaneously.

async function withBackoff<T>(fn: () => Promise<T>, max = 4): Promise<T> {
  for (let i = 0; i < max; i++) {
    try {
      return await fn()
    } catch (err: any) {
      if (err.code !== 'rate_limited' || i === max - 1) throw err
      const retryAfter = Number(err.headers?.['retry-after'] ?? 1)
      const ms = Math.min(60_000, retryAfter * 1000 * (2 ** i) + Math.random() * 1000)
      await new Promise(r => setTimeout(r, ms))
    }
  }
  throw new Error('unreachable')
}

Safe pagination ceiling

When an LLM is allowed to call list endpoints autonomously, cap the total page count to avoid runaway loops on unexpectedly-large workspaces. A simple counter is enough.

const MAX_PAGES = 20
let pages = 0
let cursor: string | undefined = undefined
do {
  if (++pages > MAX_PAGES) {
    console.warn('list ceiling hit — stopping after', MAX_PAGES, 'pages')
    break
  }
  const page = await repull.reservations.list({ cursor })
  // … process …
  cursor = page.has_more ? page.next_cursor : undefined
} while (cursor)

Custom response shapes

Send X-Schema: my-appto receive any read response in your own field names — defined once, applied server-side. Useful for keeping the LLM's tool input/output schema stable when the underlying Repull schema evolves.

curl https://api.repull.dev/v1/reservations \
  -H "Authorization: Bearer sk_test_YOUR_KEY" \
  -H "X-Schema: my-app"

Full guide: Custom Schemas.

MCP integration

For Claude / Cline / Cursor / any MCP-compatible client, repull-mcp exposes the full Repull API as MCP tools. Add a single block to your MCP config and the agent gets typed access to every endpoint.

{
  "mcpServers": {
    "repull": {
      "command": "npx",
      "args": ["-y", "@repull/mcp"],
      "env": { "REPULL_API_KEY": "sk_test_YOUR_KEY" }
    }
  }
}

See MCP Server for the full setup.

Next steps

  • Quickstart — hit your first endpoint in 60 seconds.
  • Errors — full reference with per-code fix walkthroughs.
  • Idempotency — safe retries for writes.
  • Rate Limits — quotas and headers.
  • SDKs — typed clients for the language your agent runs in.
  • MCP Server — one-line install for Claude / Cline / Cursor.
AI