Funnelfeedr Logo

Using the API

Errors

Every error is RFC 9457 problem+json with a stable code and a retryable flag. The full catalog, with what to do about each code.

When a request fails as a whole, the response has an HTTP error status and a Content-Type: application/problem+json body (RFC 9457). That holds for every error under /external/v1, including a path that does not exist (404), the wrong HTTP method (405) and a body that is not JSON (415):

{
  "type": "https://funnelfeedr.com/developers/errors/insufficient_credits",
  "title": "Not enough credits",
  "status": 402,
  "detail": "This request needs 3.4 credits; the account has 1.2. Nothing was revealed or charged. Buy credits in Funnelfeedr under Settings → Subscription, then retry.",
  "code": "insufficient_credits",
  "retryable": false,
  "creditsRequired": 3.4,
  "creditsRemaining": 1.2,
  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}
FieldMeaning
codeThe stable, machine-readable error. Branch on this.
retryabletrue when the same request may succeed if you send it again later.
statusThe HTTP status, repeated.
titleA short summary; always the same for a given code.
detailWhat went wrong this time, for humans. It may change; don't parse it.
typeA link to this code's row in the table below.
traceIdQuote it when you contact support.
errorsvalidation_failed only: the messages for each invalid field, keyed by the field's JSON path (maxCredits, items, items[3].contactId), by $ for the body as a whole, or by the header's name (Idempotency-Key).
creditsRequiredcredit_cap_exceeded and insufficient_credits: what the request would have cost. Nothing was spent.
maxCreditscredit_cap_exceeded only: the maxCredits the request carried.
creditsRemaininginsufficient_credits only: the account's balance when the request ran.
retryAfterSecondsrate_limited only: seconds until a retry can succeed, as in Retry-After.
maxItemstoo_many_items only: the most items a request may carry (25).

A single bad item in a batch is not an error response: it comes back inside a 200 as "status": "invalid" with an error object, and the rest of the batch runs. See Batching.

Catalog

codeStatusRetryableWhat to do
invalid_api_key
Invalid API key
401noThe Authorization header is missing, malformed or holds a key that does not exist. Check the FUNNELFEEDR_API_KEY value; keys start with ff_live_ and are 46 characters long.
api_key_revoked
API key revoked
401noAn admin revoked the key. Ask for a new key; do not retry.
api_key_expired
API key expired
401noThe key was rolled and its overlap period has ended. Switch to the replacement key.
feature_not_enabled
The External API is not enabled for this account
403noAPI access has not been granted to the account, or has been withdrawn. Request access from Settings → API keys.
not_found
Not found
404noNo endpoint has this path. Check the path against the API reference. A record that does not exist is not this error: lookups answer not_found per item instead.
method_not_allowed
Method not allowed
405noThe path exists but not for this HTTP method (a GET on a POST endpoint). detail and the Allow header name the method to use.
unsupported_media_type
The request body must be JSON
415noSend the body as JSON with Content-Type: application/json.
validation_failed
The request is invalid
422noThe body, a parameter or the Idempotency-Key header is invalid as a whole. errors maps each field's JSON path to its messages. Fix the request; an unusable single item comes back as status: "invalid" instead.
too_many_items
Too many items in one request
422noSend at most 25 items per request (maxItems in the body). Split the batch.
credit_cap_exceeded
The request would cost more than maxCredits
422noNothing was spent. creditsRequired in the body is what the request would cost, maxCredits the cap it carried. Ask the user before raising maxCredits.
idempotency_key_required
An Idempotency-Key header is required
400noThe call spends credits (contacts/reveal always, dry runs included; organizations/match, people/match and organizations/switchboards/match when they reveal). Send an Idempotency-Key header with a new UUID for each logical request.
idempotency_key_reused
Idempotency-Key was already used with a different request
422noThe key was used in the last 24 hours for a different method, path or body. Generate a new key for a new request.
idempotency_key_in_use
A request with this Idempotency-Key is still in progress
409yesA request with the same key is still running. Wait a moment and retry with the same key and body to get its response.
insufficient_credits
Not enough credits
402noNothing was spent. creditsRequired and creditsRemaining in the body say how far short the account is. Stop and tell the user; credits are bought in the app under Settings → Subscription. Do not retry until they have been.
rate_limited
Rate limit exceeded
429yesWait the number of seconds in Retry-After (also retryAfterSeconds in the body), then retry.
internal_error
Internal error
500yesRetry with backoff. For a spend call, retry with the same Idempotency-Key so it cannot charge twice. Quote traceId if you contact support.

New codes may be added. Treat an unknown code by its status and retryable.

Validation errors

A request that is invalid as a whole is 422 validation_failed, with each problem under errors, keyed by field. That includes a body that is not valid JSON ("$": ["The body is not valid JSON (line 1, position 12)."]), a value of the wrong type ("items[0].contactId": ["Must be a UUID."], "infoTypes[0]": ["Must be one of: email, phone, linkedIn."]) and a malformed Idempotency-Key header:

{
  "type": "https://funnelfeedr.com/developers/errors/validation_failed",
  "title": "The request is invalid",
  "status": 422,
  "detail": "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first.",
  "code": "validation_failed",
  "retryable": false,
  "errors": {
    "maxCredits": [
      "maxCredits is required when revealing. Set it to the most this request may spend, or send dryRun: true to get the price first."
    ]
  },
  "traceId": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}

Retrying

  • Retry only when retryable is true: rate_limited (after Retry-After), idempotency_key_in_use and internal_error.
  • Back off between attempts, and give up after a few.
  • On a call that spends, retry with the same Idempotency-Key and the same body, so a retry cannot charge twice. See Idempotency.
  • Never retry insufficient_credits in a loop. Stop and tell the user.