Funnelfeedr Logo

Using the API

Idempotency

Send an Idempotency-Key on every call that spends credits so a retry returns the first answer instead of charging twice.

Networks fail. If a reveal times out you cannot know whether it was charged. An Idempotency-Key makes the retry safe: Funnelfeedr remembers the first successful answer for that key and returns it again instead of running the call twice.

POST /external/v1/contacts/reveal HTTP/1.1
Host: api.funnelfeedr.com
Authorization: Bearer ff_live_...
Content-Type: application/json
Idempotency-Key: 9a3e7c1d-2b4f-4d8e-a6c0-5f1b3e9d7a28

When to send one

CallIdempotency-Key
POST /contacts/reveal, dry runs includedRequired. Without it: 400 idempotency_key_required.
POST /organizations/match, POST /people/match or POST /organizations/switchboards/match that reveals (reveal set, dryRun false)Required. Without it: 400 idempotency_key_required.
POST /organizations/match, POST /people/match or POST /organizations/switchboards/match without a reveal, or as a dry runOptional. Honoured when sent.
Every other call: the GETsIgnored.

The key

  • Any 1 to 255 visible ASCII characters; anything else is 422 validation_failed. Use a new UUID for each logical request.
  • Generate it once, before the first attempt, and reuse it for every retry of that request.
  • Idempotency keys are kept per API key: two integrations cannot collide.

What happens

SituationResult
First request with the keyRuns normally. A successful (2xx) answer is stored for 24 hours.
Same key, same request, after it succeededThe stored status and body, byte for byte, with the header Idempotent-Replayed: true. Nothing runs and nothing is charged again.
Same key while the first request is still running409 idempotency_key_in_use (retryable: true). Wait a moment and send it again to get the first request's answer.
Same key, different request422 idempotency_key_reused. Use a new key for a new request.
Same key, after the first attempt failedRuns again. Only successful (2xx) answers are stored, and a failed reveal charged nothing, so this is safe — and it is what you want after topping up following a 402.
After 24 hoursThe key is forgotten; a repeat runs as a new request.

"Same request" means the same method, path, query string and body bytes, checked per API key. Re-serialising your JSON with a different key order or spacing makes it a different request. Keep the body you sent and resend exactly that.

A retry loop

import os, time, uuid, requests

def reveal(items, info_types, max_credits):
    key = str(uuid.uuid4())  # one key for every attempt of this logical request
    body = {"items": items, "infoTypes": info_types, "maxCredits": max_credits}
    for attempt in range(5):
        response = requests.post(
            "https://api.funnelfeedr.com/external/v1/contacts/reveal",
            headers={
                "Authorization": f"Bearer {os.environ['FUNNELFEEDR_API_KEY']}",
                "Idempotency-Key": key,
            },
            json=body,
            timeout=60,
        )
        if response.ok:
            return response.json()
        problem = response.json()
        if not problem.get("retryable"):
            raise RuntimeError(f"{problem['code']}: {problem['detail']}")
        time.sleep(int(response.headers.get("Retry-After", 2 ** attempt)))
    raise RuntimeError("Gave up after 5 attempts")

requests serialises the same body dict identically on every attempt, so the retries match the first request.