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

Spending ceiling (bill_up_to_usd)

Value
Default (omit field) $1.50 (lead-session catalog price)
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
}

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”.