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
| Code | What to change in the next call | |
|---|---|---|
unauthorized | Stop. The key is missing or invalid. Ask the user for a fresh key from /dashboard/keys. | fix walkthrough → |
forbidden | The plan does not include this feature. Read err.message and surface the upgrade path; do not retry. | fix walkthrough → |
not_found | The id does not resolve in this workspace. List the parent collection and pick a real id. | fix walkthrough → |
invalid_params | A query string parameter was rejected. err.message names the parameter — fix that one and retry. | fix walkthrough → |
invalid_body | A body field was rejected. err.message names the field — patch that one field and retry. | fix walkthrough → |
unknown_params | You sent a parameter that does not exist. Drop it (or rename to the canonical name) and retry. | fix walkthrough → |
rate_limited | Wait Retry-After seconds. Pause the loop entirely until X-RateLimit-Reset. | fix walkthrough → |
internal_error | Repull-side failure. Wait 1s and retry; on second failure, exponential backoff up to 3 attempts. | fix walkthrough → |
no_connection | The user has not connected this channel. Stop and offer them the Connect URL. | fix walkthrough → |
session_expired | Mint 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.