One endpoint. Three ways in.
Everything below runs the same extractor and the same validator. Credit-billed calls draw the caller's shared Ounie wallet and are refused — never overdrawn — when it is short.
POST /api/extract
Accepts a session cookie, an Authorization: Bearer header, or ?api_key= — whichever your caller can send. All three resolve the same owner and draw the same wallet.
POST https://sitejson.ounie.com/api/extract Authorization: Bearer sjs_live_… Content-Type: application/json { "url": "https://example.com/pricing", "urls": [], // …or up to 25 instead of "url" "schema": { "type": "object", "properties": { "plan": { "type": "string" }, "price": { "type": "number" } }, "required": ["plan", "price"] }, "prompt": "only the paid tiers", // optional, ≤ 1200 chars "strict": true // default }
{ "ok": true, "run_id": "6f0c…", "status": "succeeded", "strict": true, "url_count": 1, "valid_count": 1, "billable_count": 1, "credits_reserved": 4, "credits_spent": 4, "results": [{ "url": "https://example.com/pricing", "data": { "plan": "Pro", "price": 20 }, "valid": true, "missing_fields": [], "errors": [], "used_tokens": 3910, "billed": true, "error": null }] }
Your runs, newest first. ?status= and ?limit= supported. Free.
One run with every extracted object and verdict. Free.
Public. Send schema + sample, get the verdict the billing rule uses. No auth, no upstream call, no credits.
curl -X POST https://sitejson.ounie.com/api/extract \
-H "Authorization: Bearer sjs_live_…" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/pricing",
"schema":{"type":"object",
"properties":{"price":{"type":"number"}},
"required":["price"]}}'Mint a key at /dashboard/api-keys. Five active keys per account; the raw token is shown once.
What the validator actually enforces.
A draft-07 subset, chosen to cover what extraction schemas use. The unsupported list is published rather than silently tolerated — and when your schema contains one of those keywords, the response says so in schema_warnings. A validator that quietly ignored half your contract would be billing for a guarantee it never checked.
- type (single, union, and `nullable`)
- properties · required
- items (schema form and tuple form)
- additionalProperties (false or a schema)
- enum · const
- minLength · maxLength · pattern
- minimum · maximum · exclusiveMinimum · exclusiveMaximum · multipleOf
- minItems · maxItems · uniqueItems
- anyOf · oneOf · allOf · not
- $ref · $defs · definitions
- if / then / else
- dependentSchemas · dependentRequired
- patternProperties · propertyNames
- contains · unevaluatedProperties
Inline the referenced shape instead of using $ref; schemas here are single documents, and one is easier to read than two.
- A required field returned as
nullcounts as missing unless the schema allows null (via a["string", "null"]union ornullable: true). In practice a null means “wasn't on the page”, and treating it as a hit would bill for a blank. nullablewidens a type; it does not make a field optional. The extractor is told to OMIT anything the page doesn't state rather than fill it with a placeholder, so a field that is genuinely sometimes absent should be left out ofrequired— that is the difference between “this run is thin” and “this plan happens to be priced on request”.- Field
descriptions are sent to the extractor and steer it. A description like “monthly price in USD, excluding tax” is the cheapest accuracy you can buy.
Streamable HTTP at /api/mcp
Works with Claude, Cursor, ChatGPT, the AI SDK and the Ounie AI Team. Hosts that can't set a static header can pass the key in the URL: ?api_key=sjs_live_…. Legacy SSE lives at /api/sse.
| Tool | What it does | Cost |
|---|---|---|
| extract_structured | Turn one or more URLs into JSON matching a schema you supply. Returns { data, valid, missing_fields } per URL. | 4 credits per billable URL |
| get_run | Fetch a past run by id, with every extracted object. | free |
| list_runs | The caller's runs, newest first. | free |
| check_schema | Dry-run a schema against a sample object through the exact validator that decides billing. No auth. | public · free |
| get_credit_balance | Spendable Ounie credits and how many URLs that buys. | free |
| get_pricing | Credit price, billing rule and the x402 endpoint. No auth. | public · free |
| whoami | The authenticated key's owner and key id. | free |
{ "mcpServers": { "structured-web": { "url": "https://sitejson.ounie.com/api/mcp", "headers": { "Authorization": "Bearer sjs_live_…" } } } }
Running short on credits returns an insufficient_credits tool error with a top-up link rather than a partial run — an agent can spend down to zero and no further.
POST /api/x402/extract
Keyless pay-per-call in USDC on Base — $0.048 per URL, one URL per call, no Ounie account. Send the request without a payment header to receive the terms; sign and retry with X-Payment.
The order matters here more than anywhere else in the product. An x402 settlement is final on chain and there is no refund path, so the payment is only verified first — cheap, off-chain, no funds move — then the extraction runs, and the settlement happens only if the result is billable. A URL that scraped cleanly but failed your schema under strict returns 422 with charged: false and the partial object. You pay for answers, not attempts, on this rail too.
{ "x402Version": 1, "accepts": [{ "scheme": "exact", "network": "base", "maxAmountRequired": "48000", "resource": "https://sitejson.ounie.com/api/x402/extract", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "payTo": "0x…", "maxTimeoutSeconds": 60, "extra": { "name": "USD Coin", "version": "2" } }] }
{ "ok": true, "payment": { "tx_hash": "0x…", "payer": "0x…", "paid_atomic_usdc": "48000" }, "strict": true, "url": "https://example.com/pricing", "data": { "plan": "Pro", "price": 20 }, "valid": true, "missing_fields": [], "billed": true }
{ "ok": false, "charged": false, "reason": "schema_mismatch", "detail": "the extraction did not satisfy your schema; under strict this is not charged", "result": { "data": { "plan": "Pro" }, "valid": false, "missing_fields": ["price"] } }
What each one means, and whether it cost you.
| Code | HTTP | Meaning |
|---|---|---|
| unauthorized | 401 | No session cookie and no valid Bearer key. |
| insufficient_credits | 402 | Wallet can't cover urls × 4. Body carries required_credits, balance_credits and buy_credits_url. Nothing ran. |
| url_required / invalid_url | 400 | Pass `url` or `urls`; each must be a public http(s) address. |
| schema_required / schema_too_large | 400 | `schema` must be a JSON Schema object under 20,000 characters. |
| too_many_urls | 400 | At most 25 URLs per run. |
| too_many_running | 429 | 5 runs already in flight for this account. |
| extraction_unavailable | 503 | The extractor is temporarily unavailable. Nothing was charged. |
| extraction_failed | 502 | The run failed after reserving. Credits were refunded in full. |
Per-URL failures inside a successful run are not errors at the HTTP level — they come back in results[].error with billed: false, so one dead link never sinks a batch of twenty-five. Top up on ounie.com.