Async enrich jobs

Use POST /api/v1/enrich/jobs for deferred billing after long-running enrich work. Poll GET /api/v1/enrich/jobs/{job_id} with the poll_bearer from the 202 body (free).

When to use

Sync routes (POST /api/v1/enrich/websearch, POST /api/v1/enrich/lead-session) remain unchanged for one-shot calls.

Supported routes

Full sessions

Partial lead-session ops (same payloads as sync POST /api/v1/enrich/lead-session/… routes; hosted demo submits these via jobs only):

route Default precharge (omit bill_up_to_usd)
enrich/lead-session/slot-rerun $0.35
enrich/lead-session/slot-person $0.35
enrich/lead-session/slots-rerun N × $0.35 (N = payload.model_ids count)
enrich/lead-session/synthesize-patch $0.08
enrich/lead-session/finding-rerun catalog price
enrich/lead-session/sic-rerun catalog price

Partial routes use bill-on-match: no usable enrichment → $0 settle and full virtual precharge refund. Recipe for batch hunts: lead_session_followups.

Spending ceiling (bill_up_to_usd)

Value
Default (omit field) Route catalog price — $1.50 for websearch / lead-session; $0.35 per slot-rerun or slot-person; N × $0.35 for slots-rerun
Client override $0.05$50.00 (ENRICH_JOB_BILL_UP_TO_MAX_USD)
Charged Actual vendor cost ≤ ceiling; unused precharge refunded
No-match $0 on upto path; full refund on virtual precharge

For async lead-session jobs, in-payload budget_usd is capped to the job’s bill_up_to_usd (sync lead-session alone still caps budget_usd at $20 per call; session header budget up to $50).

Two billing paths

A — Virtual balance (wallet / xpay agents)

For agents with a bearer (411_… or 411k_…) and prepaid virtual_balance_usd:

  1. POST /api/v1/enrich/jobs with Authorization: Bearer <token> (no PAYMENT-SIGNATURE).
  2. Server pre-charges bill_up_to_usd from virtual balance (default $1.50).
  3. Receive 202 with job_id, poll_bearer, billing_mode: "virtual_balance".
  4. Poll GET /api/v1/enrich/jobs/{job_id} with Authorization: Bearer <poll_bearer>.
  5. On complete, server refunds unused precharge to virtual balance (full refund on no-match).

B — CDP x402 upto (anonymous or balance-exhausted)

When no bearer is sent (or balance cannot cover bill_up_to_usd):

  1. POST without payment → 402 with scheme: "upto" (CDP only), maxTimeoutSeconds: 1200.
  2. Sign Permit2 upto and retry with PAYMENT-SIGNATURE.
  3. Receive 202 with billing_mode: "x402_upto".
  4. Poll with poll_bearer.
  5. Server settles on-chain after the job completes (actual ≤ max; $0 on no-match).

xpay does not advertise upto — do not expect xpay entries in accepts[].

Example — websearch job

{
  "route": "enrich/websearch",
  "payload": {
    "business_name": "Acme Corp",
    "city": "Tampa",
    "state": "FL"
  },
  "bill_up_to_usd": 1.5,
  "callback_url": "https://example.com/hooks/enrich"
}

Example — lead-session job

{
  "route": "enrich/lead-session",
  "payload": {
    "lead": {
      "contact_id": 81555753,
      "company": "Haney Furniture Co",
      "street": "30 N Beaver St",
      "city": "New Castle",
      "state": "PA",
      "zip": "16101",
      "number1": "7246547732"
    },
    "budget_usd": 2.0,
    "max_rounds": 5,
    "mode": "auto"
  },
  "bill_up_to_usd": 5.0
}

Example — batch slot rerun

{
  "route": "enrich/lead-session/slots-rerun",
  "payload": {
    "lead": { "contact_id": 1737992709, "company": "Hoops & Gears and Clark" },
    "chat_session_id": "ab62eb36cd134d70a58bc174d6b09185",
    "model_ids": ["data411-hunt-ground", "data411-crimson-span"],
    "prior_backfill": { "...": "current backfill" }
  },
  "bill_up_to_usd": 0.70
}

See lead_session_followups for batch slot eligibility (pending / error in trace; skip done; allow never-ran catalog slots), person hunt per contact (slot-person, hosted Hunt controls), and hunt-board vs trace notes.

Example — slot-person (one contact)

{
  "route": "enrich/lead-session/slot-person",
  "payload": {
    "lead": { "contact_id": 1737992709, "company": "Hoops & Gears and Clark" },
    "chat_session_id": "ab62eb36cd134d70a58bc174d6b09185",
    "target_contact_id": "c_owner_1",
    "target_name": "Jane Doe",
    "prior_backfill": { "...": "current backfill" },
    "include_person_hunt": true
  },
  "bill_up_to_usd": 0.35
}

Intermediate search runs data411-hunt-person; when include_person_hunt is true and person hunt has not already run for that contact, the worker chains whitepages_person_hunt before returning. Hosted UI Intermediate search uses this route only — contact-name clicks use full enrich/lead-session follow-up when hunt is still pending.

Batch eligibility (slots-rerun)

When payload.model_ids is set, the worker filters each id against prior_backfill.tool_trace:

Omit model_ids to run all trace-eligible pending/error slots. Default precharge N × $0.35 uses len(payload.model_ids) at submit time (minimum one slot price when the field is omitted).

202 response fields

Field Meaning
job_id ej_… — pass to poll URL
poll_bearer 411j_… — scoped poll token
poll_interval_sec Suggested poll interval (default 2)
billing_mode virtual_balance or x402_upto
bill_up_to_usd Ceiling pre-charged / authorized
max_timeout_seconds Upto timeout (default 1200)

Poll response

Poll until status is completed or failed.

status Meaning
queued Accepted, not started
running Worker executing
completing Finishing billing / persist
completed Done — see result
failed Error — see error
billing_status Meaning
settled_virtual Virtual precharge true-up complete
settled On-chain upto settle complete
settling Settlement in progress
settle_failed Billing failed after run

result mirrors the sync route JSON when complete.

Optional fields

MCP

submit_enrich_job and get_enrich_job wrap the same paths. Anonymous CDP upto is REST only (MCP requires bearer for submit).

Smoke scripts

python3 scripts/smoke_enrich_upto_jobs.py --anon
BASE_URL=https://411data.io python3 scripts/smoke_enrich_upto_jobs.py --balance --route websearch
BASE_URL=https://411data.io python3 scripts/smoke_enrich_upto_jobs.py --upto --route websearch

Discovery

Listed at GET /discovery/resources and /.well-known/x402 as a separate upto resource (not in sync PAID_ROUTES). Bazaar search: try “async”, “queue”, “jobs”.