Funnelfeedr Logo

Get started

Guidance for AI agents

Rules for an AI agent that calls the Funnelfeedr API on someone's behalf — which calls spend credits, how to quote before revealing, and how to stop safely.

Every API key can spend credits, so an agent with a key can turn a customer's credits into organization and contact details. Credits are money, and a reveal cannot be undone. This page is the contract an agent should follow; the same rules are in /developers/llms.txt and in the snippet for your agent's prompt.

The rules

  1. Only reveals spend credits: POST /contacts/reveal, and POST /organizations/match, POST /people/match or POST /organizations/switchboards/match with reveal set and dryRun false. Matching, contacts, switchboards and the balance are free.
  2. Before any reveal, send the same request with dryRun: true, show the user the quoted cost (creditsQuoted), and reveal only after they approve it.
  3. Always set maxCredits on a reveal to the amount the user approved. A request that would cost more fails with credit_cap_exceeded and spends nothing.
  4. Always send an Idempotency-Key header (a new UUID per logical request) on a call that spends, and on every POST /contacts/reveal, dry runs included. Reuse the same key, with the same body, only to retry that same request.
  5. Never loop reveals one item at a time and never reveal the same contact or organization twice. Batch up to 25 items in one request.
  6. On 402 insufficient_credits, stop and tell the user. Nothing was spent; do not retry until they have topped up.
  7. On an ambiguous match, ask the user which candidate they mean, or pick one by id from your own data. Never reveal every candidate.
  8. On 429 rate_limited, wait the number of seconds in Retry-After before retrying. Retry other errors only when the body says "retryable": true.
  9. Read the key from the FUNNELFEEDR_API_KEY environment variable. Never print, log or commit it, and never send it from a browser or mobile app.

Which calls spend credits

CallSpends?
POST /organizations/match without reveal, or with dryRun: trueNever. The basics come back; the details only if the account already revealed them.
POST /organizations/match with reveal: ["details"] and without dryRunYes, for the organizations whose details it reveals
POST /organizations/contacts/matchNever. Details come back masked unless the account already revealed them.
POST /organizations/switchboards/match without reveal, or with dryRun: trueNever
POST /organizations/switchboards/match with reveal and without dryRunYes, for the switchboard numbers and emails it unmasks
POST /people/match without reveal, or with dryRun: trueNever
POST /people/match with reveal and without dryRunYes, for the email and phone numbers it unmasks
POST /contacts/reveal with dryRun: trueNever (the call still needs an Idempotency-Key)
POST /contacts/revealYes
GET /creditsNever

Only an organization's details (0.2 credits per organization by default), a contact's email (0.2) and phone (1.0) cost anything; a contact's LinkedIn URL is never masked and never charged. Details the account has already revealed cost nothing again, and a request that finds nothing charges nothing. The OpenAPI spec marks the operations that can spend with an x-credit-cost extension.

A safe reveal, step by step

  1. Find the people for free. Use people/match without reveal, or organizations/contacts/match (which lists only the people matching the account's target contact roles, for up to 25 organizations a call), to get contact ids. Keep only the people the user actually needs. To call an organization rather than a person, find its switchboard with organizations/switchboards/match.
  2. Quote. Send the reveal with dryRun: true. The response's creditsQuoted is the exact price, and each result's reveal.creditCost that person's price. A person who appears twice is charged once but shows the price on both results, so show the user creditsQuoted, not a sum of the results.
  3. Ask. Show the user the number of people, what will be revealed and the price, and wait for a yes. A user's standing instruction ("reveal emails for up to 50 credits a day") counts — keep a running total against it.
  4. Reveal once. Send the same request with dryRun: false, maxCredits set to the approved amount and a new Idempotency-Key.
  5. Check the answer. Read creditsCharged and creditsRemaining and report them. Do not reveal the same contacts again.

The reveal in step 4, as a POST /contacts/reveal body:

{
  "items": [
    { "ref": "anna", "contactId": "c0a8012e-5b1f-4e3d-9a7c-2f6b8d4e1a90" },
    { "ref": "johan", "contactId": "d41d8cd9-8f00-4b20-9e98-0ecf8427e1b2" }
  ],
  "infoTypes": ["email", "phone"],
  "maxCredits": 1.4,
  "dryRun": false
}

Organization details follow the same steps. Match for free first and check detailsRevealed: an organization the account already revealed returns its details without a reveal. Quote the rest with organizations/match, reveal: ["details"] and dryRun: true (creditsQuoted is the total; each result's creditCost is that organization's price, shown on every result for an organization that appears more than once but charged once), ask, then reveal once with maxCredits and an Idempotency-Key.

When to stop

You getDo this
402 insufficient_creditsStop. Nothing was spent. Tell the user the balance is too low; they top up in the app. Do not retry in a loop.
422 credit_cap_exceededStop. Nothing was spent. The body's creditsRequired is the real cost; ask the user before raising maxCredits.
429 rate_limitedWait Retry-After seconds, then continue.
409 idempotency_key_in_useThe same request is still running. Wait a moment, then retry with the same key to get its answer.
5xxRetry a few times with backoff. On a spend call, reuse the same Idempotency-Key so a retry cannot charge twice.

Branch on the code field, never on the detail text: detail is for humans and may change.

Ambiguous and missing matches

  • ambiguous means more than one person fits. The response lists up to five candidates with their id, name, job title and organization. Ask the user which one they mean, or pick by id if your own data settles it (a matching title, say). Never reveal all of them to be safe.
  • not_found is free and final for that input. Funnelfeedr does not guess: nicknames ("Kalle" for "Karl") do not match in v1. Try another identifier you have — an email or a LinkedIn URL — rather than variations of the name. An email only matches an address the account has already revealed or owns, so a not_found email says nothing about whether the address exists; try the name and organization or the LinkedIn URL.

See Matching for full examples of each.

Batch, don't loop

Every lookup takes up to 25 items. Send one request with 25 people or organizations rather than 25 requests with one, and use ref to line answers up with your rows. A reveal is all-or-nothing per request, so a batch is either fully charged or not charged at all. See Batching.