dataduct

HTTP 402 · Payment Required

Open data, priced per request,
paid by the agent that wants it.

dataduct ingests public-domain and permissively-licensed datasets, curates them into its own store, and sells single records over x402. No account, no API key, no subscription — an agent asks, gets a price, pays, and is served.

We sell access and curation. The data stays open, and every licence obligation attached to it travels through to your response.

24datasets curated
3purchasable on this deployment
11ingest endpoints re-verified live
3blocked, and documented as such

What this is

dataduct ingests open datasets — public-domain and permissively-licensed — into its

own store, and sells individual records to whoever asks. Payment happens in the

request itself over x402: no account, no API key, no contract,

no minimum. An agent that has never seen this service before can discover a price

and complete a purchase in two HTTP round trips.

We are not selling the data. The data is open and you can go and get it yourself.

We are selling access and curation: one cleaned, deduplicated, licence-checked

copy behind a uniform interface, so you don't write and maintain 24 different

ingest pipelines against 24 providers who each change their formats without notice.

What you can rely on

You are never charged for a response you didn't get. Settlement runs only after

the handler produces a 2xx. A 404, a 400 or a 500 costs you nothing, and a

capability this deployment doesn't offer returns 501 *before* any payment is

requested.

A failed settlement gets you nothing. If your payment doesn't settle, you get

402 and no data — never a record you didn't pay for, and never a receipt for a

payment that didn't happen.

Retries don't double-charge. Present the same authorization for the same

resource and the original receipt is replayed rather than a second settlement

attempted. If your connection drops after paying, just retry.

Licence obligations travel with the data. Records that can't prove their licence

never reach the store, and a record from a CC-BY or OGL source carries the exact

attribution string you must pass downstream. That is a real obligation on you, not

a courtesy field.

What this isn't, yet

This deployment is not open for business. Pricing is placeholder, no payment

facilitator has been selected, and the analytical query tier is switched off. The

catalogue below shows exactly what is and isn't available rather than what is

planned — see Availability for the specifics.

Catalogue

Every dataset, its licence tier, what it costs, and whether this deployment will actually sell it. A source that is not enabled returns 501 before any payment is requested — you are never charged to discover that something is unavailable.

iddatasetlicenceendpointslookup priceingest
mitre_cve MITRE CVE ListAuthoritative CVE identifier dictionary CC-BYattr lookup 0.0001 verified earlier
nvd NVD (NIST)CVE records, CVSS scores, CPE platform enumeration - canonical vuln DB PD lookup 0.0001 verified earlier
usaspending USAspending.govEvery federal contract, grant, and award; bulk export by agency, award type, fiscal year (back to FY2008) PD lookupquery 0.001 verified earlier
bls_comp BLS NCS / ECI / ECECBenefits participation, employer costs, compensation-cost trends PD lookupquery 0.0002 verified live
bls_oews BLS OEWSWage & employment data for 800+ occupations by state and metro area PD lookupquery 0.0002 verified live
cap Caselaw Access Project (Harvard)6.7M US federal & state cases spanning ~360 years PD lookupsearch 0.0001 verified live
census_cps Census / CPSHousehold income, demographics, labor-force statistics PD lookupquery 0.0002 blockedconnector still fetches the HTML catalog page. Target is https://api.census.gov/data/{year}/cps/asec/mar but api.census.
cfpb_ccdb CFPB Consumer Complaint Database13.8M+ complaints vs financial companies: company responses, product type, issue narratives PD lookupsearchquery 0.0001 never verified
cfpb_hmda CFPB HMDA (mortgage) dataHome Mortgage Disclosure Act lending data PD lookupquery 0.0002 verified live
circl_cve CIRCL CVE feedDaily CVE/CPE JSON dumps PD lookup 0.0001 never verified
courtlistener CourtListener (Free Law Project)Every US court opinion, dockets, oral arguments, judge bios, financial disclosures CC0 lookupsearch 0.0002 never verified
cuad CUAD (Atticus Project)Expert-annotated commercial contract clauses & deal points CC-BYattr lookupsearch 0.002 verified live
cvefixes CVEfixes12K+ vuln-fixing commits with before/after code across 4,249 OSS projects MIXED lookupquery 0.0002 blockedconnector still fetches the HTML record page. The JSON resolver is https://zenodo.org/api/records/4476563, but the only
cwe CWE (MITRE)Common Weakness Enumeration taxonomy CC-BYattr lookup 0.0001 verified live
data_europa data.europa.eu (EU Data Portal)Catalog aggregating tens of thousands of EU / member-state datasets MIXED lookupsearch 0.001 verified live
data_gov data.govCatalog aggregating tens of thousands of US government datasets MIXED lookupsearch 0.001 never verified
fdic_ffiec FDIC / FFIEC Call ReportsUS bank financial-condition & regulatory call-report data PD lookupquery 0.0002 never verified
fred FRED (St. Louis Fed)800K+ economic time series aggregated from many providers MIXED lookupquery 0.0002 blockedendpoint requires FRED_API_KEY (400 "Variable api_key is not set" without one) and cannot enumerate series. No
govinfo govinfo / eCFR / US CodeFederal legislation, CFR regulations, congressional records, US Code PD lookupsearch 0.0002 verified live
legislation_uk legislation.gov.ukUK Acts of Parliament, statutory instruments, devolved legislation OGLattr lookup 0.001 verified live
onet O*NET (US Dept of Labor)1,000+ occupations: skills, tasks, tools, knowledge, work context, education CC-BYattr lookupsearch 0.001 verified live
sam_fpds SAM.gov Contracting / FPDSContract opportunities and award records PD lookupquery 0.001 never verified
sec_edgar SEC EDGARAll US public-company filings since 1993: 10-K, 10-Q, 8-K, DEF 14A proxy, Forms 3/4/5 insider trades, XBRL fin PD lookupquery 0.001 verified earlier
world_bank World Bank Open DataGlobal economic & development indicators, thousands of time series CC-BYattr lookupquery 0.001 verified live

Prices are in the settlement asset's units, per request. enabled endpoints are live; greyed ones are registered in code but gated off on this deployment. Machine-readable: catalog.json.

The payment flow

Two round trips. The first tells you the price; the second pays it.

1 — Ask, and get a price

Any paid endpoint without an X-PAYMENT header answers 402 with an

x402 v1 challenge:

{
  "x402Version": 1,
  "error": "X-PAYMENT header is required",
  "accepts": [{
    "scheme": "exact",
    "network": "base",
    "maxAmountRequired": "100",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
    "resource": "https://api.example.com/v1/nvd/CVE-2024-12345",
    "description": "nvd record CVE-2024-12345",
    "mimeType": "application/json",
    "maxTimeoutSeconds": 60,
    "extra": { "name": "USD Coin", "version": "2" }
  }]
}

Three fields are worth reading carefully, because they are where integrations go

wrong:

0.0001 USDC is "100". It is a string because a uint256 does not fit in a

double. Do not parse it as a number.

signs against.

per network — USDC on Base mainnet is "USD Coin", on Base Sepolia it is

"USDC" — and a wrong value produces a signature the facilitator rejects.

2 — Pay, and be served

Sign an EIP-3009 transferWithAuthorization, wrap it in a PaymentPayload,

base64 it, and resend the identical request with an X-PAYMENT header. The

payload decodes to:

{
  "x402Version": 1,
  "scheme": "exact",
  "network": "base",
  "payload": {
    "signature": "0x…",
    "authorization": {
      "from": "0xYourWallet",
      "to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
      "value": "100",
      "validAfter": "1753500000",
      "validBefore": "1753503600",
      "nonce": "0x…32 bytes…"
    }
  }
}

On success you get 200, the record, and an X-PAYMENT-RESPONSE header — base64

of the settlement result:

{ "success": true, "transaction": "0x…", "network": "base", "payer": "0xYourWallet" }

What the server checks before it charges you

The facilitator is treated as a signature and settlement oracle, not as the

authorization decision. Before any facilitator call, the request is rejected

locally if:

ConditionResponse
X-PAYMENT is not base64 JSON, or is structurally invalid402X-PAYMENT is not base64-encoded JSON
scheme or network differ from the challenge402scheme "…" does not match required "exact"
authorization.to is not the quoted payTo402authorization.to is not the required payTo address
authorization.value is below the quote402authorization.value N is below the required M atomic units
the authorization window does not cover now402authorization has expired / is not yet valid

Overpayment is accepted. Paying someone else and presenting the receipt here is

not.

Ordering, and why it matters to you

The handler runs first; settlement is the last thing before the body is released.

That single ordering is what produces the guarantees:

are not charged.

costs you nothing.

Idempotency and replay

Payments are keyed on the EIP-3009 nonce.

replayed, no second settlement is attempted, and the response carries

X-Payment-Idempotent-Replay: true. Safe to retry after a dropped connection.

one resource.

This is defence in depth rather than the primary control: under the exact scheme

the nonce is consumed on-chain at settlement, so the chain itself rejects a

genuine double-spend.

Status codes

Read these precisely — several mean "not your fault".

CodeMeaning
200Paid and served.
402Payment required, invalid, or not settled. The body is a fresh challenge you can pay.
404The record is not in our store. Not charged. We never proxy a provider, so a 404 means we don't have it, not that it doesn't exist.
429Rate limited. Retry after the Retry-After interval.
501This deployment does not offer that capability or source. Nothing was charged and no price was quoted.
503Our payment provider or configuration is broken. Not your fault; retry later.

The 402/503 split is deliberate: an outage on our side must not look like a

routine payment challenge.

API reference

Base path /v1. Everything under it is payment-gated; everything outside it is free.

GET / — catalogue

Free. Returns every source with its licence tier, registered capabilities, price

bands and whether this deployment will sell it.

{
  "service": "dataduct",
  "x402Version": 1,
  "sourceCount": 24,
  "enabledCount": 3,
  "sources": [{
    "id": "nvd",
    "license_tier": "PD",
    "enabled": true,
    "capabilities": { "lookup": true, "search": false, "query": false, "attribution": false },
    "price": { "lookup": "0.0001", "search": "0.001", "query": "0.002" }
  }]
}

capabilities reflects this deployment, not the code: a capability the connector

registers but which is gated off reads false. Prefer

catalog.json if you are consuming this programmatically — it is

the same data plus resource-id formats and blocked reasons.

GET /readyz — readiness

Free. 200 when the deployment can safely take payment; 503 otherwise, with the

failing check named. **A 402 from a paid route does not imply the payment path

works** — the challenge is emitted before configuration is validated. This is the

endpoint that answers that question.

GET /v1/{source}/{resource} — point lookup

Returns one record. The {resource} format differs per source and is the single

thing most worth checking before you integrate — see the resourceIdFormat field

in catalog.json.

{
  "sourceId": "nvd",
  "recordId": "CVE-2024-12345",
  "license": "PD",
  "fetchedAt": "2026-07-20T06:00:00.000Z",
  "data": { }
}

attribution is present only for CC-BY and OGL sources, and when present it is an

obligation you must carry downstream.

POST /v1/{source}/search — semantic search

{ "query": "privilege escalation in container runtimes", "topK": 10 }

query is required; an empty or missing one is 400. topK is clamped to the

source's configured maximum (25 unless the connector sets otherwise) — values

above it are silently reduced rather than rejected. The response carries

{ source, query, count, results[] }, each result shaped like a lookup response.

POST /v1/{source}/query — analytical

Returns 501 on this deployment, before any payment challenge. No analytical

engine is bound. Twelve connectors register the capability, and it is gated off

for all of them rather than accepting payment for a query it cannot run.

When an engine is bound, request bodies are whitelisted per source — never raw

SQL. Five connectors (usaspending, cvefixes, fdic_ffiec, fred,

sam_fpds) explicitly reject a sql key with 400. Results are clamped to

1,000 rows / 1 MiB by default, tightened per source, and a clamped response sets

truncated: true.

Headers

HeaderDirectionNotes
X-PAYMENTrequestbase64 PaymentPayload.
X-PAYMENT-RESPONSEresponsebase64 SettlementResponse, on settled requests.
X-Payment-Idempotent-Replayresponsetrue when a retry replayed a stored receipt.
X-Request-IdbothEchoed if you send one, generated otherwise. Quote it in any support request.
Cache-Controlresponseprivate, no-store on paid bodies. Do not cache them in a shared cache.

CORS is open, and X-PAYMENT-RESPONSE / X-Request-Id are exposed, so a

browser-based agent can both send payment and read the receipt.

Errors

Every error body is JSON with an error field. Those that represent a fault on

our side also carry requestId — that is what reconciliation and support are keyed

on, so log it.

errorStatusMeaning
payment_required (as an x402 challenge body)402Pay and retry.
unknown_source404Not a source we carry.
not_found404We don't hold that record. Not charged.
not_implemented501Capability or source unavailable here. Not charged.
bad_request400Malformed body; reason says what.
rate_limited429Retry after Retry-After.
service_unavailable503Our payment provider or config. Includes requestId.
internal_error500Unhandled. Includes requestId. Not charged.

Integrate

Try it now, for free

The catalogue, readiness and the 402 challenge all work without paying anything.

# What's available, and what does it cost?
curl -s https://<host>/ | jq '{enabledCount, sources: [.sources[]|select(.enabled)]|map({id,price})}'

# Can this deployment safely take money?
curl -s https://<host>/readyz | jq

# Ask for a record without paying — this is the price quote.
curl -i https://<host>/v1/nvd/CVE-2024-12345

That last call returns 402 with the challenge. Nothing is charged, and you can

call it as often as the rate limit allows to discover pricing.

Build the payment header

The challenge is x402 v1, so the standard clients (x402-fetch, x402-axios)

apply. If you are constructing it yourself, the header is base64 of the payload —

sign the authorization with your wallet, then:

// Build X-PAYMENT from a signed EIP-3009 authorization.
function paymentHeader({ signature, authorization, network = 'base' }) {
  const payload = { x402Version: 1, scheme: 'exact', network, payload: { signature, authorization } };
  const bytes = new TextEncoder().encode(JSON.stringify(payload));
  let binary = '';
  for (const b of bytes) binary += String.fromCharCode(b);
  return btoa(binary);
}

// Read the receipt back off a served response.
function settlement(res) {
  const h = res.headers.get('X-PAYMENT-RESPONSE');
  if (!h) return null;
  const binary = atob(h);
  const bytes = Uint8Array.from(binary, (c) => c.charCodeAt(0));
  return JSON.parse(new TextDecoder().decode(bytes));
}

Take value, to, network and the EIP-712 domain straight from the challenge's

accepts[0] — do not hardcode them. maxAmountRequired is already in atomic

units; pass it through as authorization.value unchanged.

Retry safely

If a request fails after you sent payment, resend it **with the same nonce and the

same URL**. You will get the original receipt back and will not be charged twice.

Changing either the nonce or the resource makes it a new purchase.

Carry attribution downstream

Six sources are CC-BY or OGL. Their responses include an attribution string, and

passing it on is a licence condition — not a formatting nicety.

{
  "sourceId": "onet",
  "recordId": "15-1252.00",
  "license": "CC-BY",
  "attribution": "This product incorporates data from O*NET, developed by the U.S. Department of Labor/Employment and Training Administration (USDOL/ETA), used under the CC BY 4.0 license.",
  "data": { }
}

The guarantee runs the other way too: a record that cannot prove its licence is

dropped before it ever reaches the store, and a CC-BY record that somehow lost its

attribution fails at the serving boundary rather than being served bare. So an

absent attribution on a CC-BY source is a bug worth reporting, not an optional

field.

Rate limits

120 requests per minute per IP on /v1/*. Over the limit returns 429 with

Retry-After: 60. The free catalogue and /readyz are not limited.

Before you build against this

Read Availability. Three sources are currently purchasable, the

analytical tier is off, prices are placeholder, and no payment facilitator is

configured — so the paid path cannot complete end to end today. The free surface,

the challenge format and the record shapes are stable enough to build against.

Availability

This page is generated from the repository, so the numbers here are the running

configuration rather than an intention.

Three of 24 sources are purchasable on this deployment (nvd, mitre_cve,

usaspending). The rest return 501 before any payment challenge. A source is

added to that list only after its ingest has actually been run — not when its

connector compiles.

Ingest verification is reported in four states, because collapsing them would

overstate the position:

2026-07-26.

re-confirmed in it. All three currently-sellable sources are in this state.

The analytical /query tier is off. No engine is bound, so it returns 501

rather than charging for a query it cannot run.

Pricing is placeholder. The bands shown are not commercial prices. One on-chain

settlement occurs per paid request, so a price below the facilitator's fee would be

loss-making; real numbers depend on a facilitator decision that has not been made.

No payment facilitator is configured. Until one is, the paid path cannot

complete: you will see a correct 402 challenge, and /readyz will tell you the

deployment is not ready to take money.

Three sources are blocked outright. cvefixes is blocked for a structural reason

worth stating plainly: its only distribution is a 12.6 GB gzip inside a 12.7 GB

ZIP, and gzip has no random access, so it cannot be ingested in a Workers runtime

at all. fred and census_cps need provider API keys.