forbidden
Authenticated, but the workspace does not have access to this resource or feature.
When it fires
The request was authenticated, but the workspace is not entitled to call this endpoint. Common reasons:
- Calling a market-intelligence endpoint (
/v1/markets,/v1/markets/{city}/calendar) on a plan that does not include market data. - Calling an AI-tier endpoint with a free-tier key.
- Reading or changing Booking.com notification subscriptions with
GET,POSTorDELETE /v1/channels/booking/webhooks. These endpoints are deprecated and always return403 forbidden: the subscriptions belong to the Repull platform account every workspace shares. Booking.com events for your own properties are delivered through Repull webhooks — subscribe withPOST /v1/webhooks. - The API key was scoped to read-only and the request is a write.
A channel write that fails because of the connection is not forbidden. On PUT /v1/channels/airbnb/listings/{id}/pricing and /availability: no Airbnb account connected is 404 no_connection; a listing id with no Airbnb connection in this workspace is 404 not_found; and a connection Airbnb no longer accepts for that listing is 403 connection_reauth_required — reconnect at https://repull.dev/dashboard/connections.
Response shape
Every Repull error follows the same envelope. The code is stable and safe to switch on.
{
"error": {
"code": "forbidden",
"message": "<human-readable explanation of what went wrong>",
"docs_url": "https://repull.dev/docs/errors/forbidden"
}
}How to fix
- Read the `message` field — it tells you exactly which capability is missing.
- Open /dashboard/usage to confirm which tier the workspace is on.
- If the missing capability is a paid add-on (markets, AI), upgrade the workspace from /dashboard/billing.
- If you are sure the workspace should have access, double-check you are using the right key — keys are workspace-scoped.
- If the request was to `/v1/channels/booking/webhooks`, stop: it cannot succeed. Subscribe with `POST /v1/webhooks` instead.
Common gotchas
forbiddenis distinct fromunauthorized. The key is valid; it just does not have permission. Retrying with the same key will keep failing.- Some entitlement gates only check on the first request of a billing cycle. If you upgraded mid-cycle and are still seeing the error, the next call should succeed once the entitlement cache refreshes (under a minute).
Examples
curl
# Free-tier key calling a markets endpoint
curl https://api.repull.dev/v1/markets/london \
-H "Authorization: Bearer sk_live_FREE_TIER_KEY"
# {
# "error": {
# "code": "forbidden",
# "message": "Markets data requires the Atlas add-on. Upgrade at /dashboard/billing."
# }
# }TypeScript
// The SDK has no single-market method, so call the endpoint directly.
const res = await fetch('https://api.repull.dev/v1/markets/london', {
headers: { Authorization: `Bearer ${process.env.REPULL_API_KEY}` },
})
if (!res.ok) {
const { error } = await res.json()
if (error.code === 'forbidden') {
// Don't retry — surface the entitlement gap to the user
console.error('Plan upgrade required:', error.message)
return { needsUpgrade: true, reason: error.message, fix: error.fix }
}
throw new Error(`${error.code}: ${error.message}`)
}If you're an AI agent
The user's plan does not include the feature you tried to call. Do not retry. Read error.message and tell the user which add-on or upgrade they need, then offer them /dashboard/billing as the next step.
Related
- Error reference — the full table of error codes
- Using Repull from AI agents — patterns for handling errors in agent loops
- AI Pricing & plans
- Markets data
Hit an error that isn't covered? Email hello@repull.dev with the request id from the response headers.