StructuredWeb
API docs

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.

01REST

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.

REQUEST
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
}
RESPONSE · 200
{
  "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
  }]
}
GET /api/runs

Your runs, newest first. ?status= and ?limit= supported. Free.

GET /api/runs/[id]

One run with every extracted object and verdict. Free.

POST /api/check-schema

Public. Send schema + sample, get the verdict the billing rule uses. No auth, no upstream call, no credits.

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

02Schema support

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.

Enforced
  • 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
Not enforced — reported
  • $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.

Two behaviours worth knowing
  • A required field returned as null counts as missing unless the schema allows null (via a ["string", "null"] union or nullable: 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 of required— 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.
03MCP

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.

ToolWhat it doesCost
extract_structuredTurn one or more URLs into JSON matching a schema you supply. Returns { data, valid, missing_fields } per URL.4 credits per billable URL
get_runFetch a past run by id, with every extracted object.free
list_runsThe caller's runs, newest first.free
check_schemaDry-run a schema against a sample object through the exact validator that decides billing. No auth.public · free
get_credit_balanceSpendable Ounie credits and how many URLs that buys.free
get_pricingCredit price, billing rule and the x402 endpoint. No auth.public · free
whoamiThe authenticated key's owner and key id.free
claude_desktop_config.json
{
  "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.

04x402

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.

Verify → extract → settle

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.

1 · TERMS (no X-Payment) → 402
{
  "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" }
  }]
}
2 · RETRY WITH X-Payment → 200
{
  "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
}
…or 422 when the result is thin
{
  "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"] }
}
05Errors

What each one means, and whether it cost you.

CodeHTTPMeaning
unauthorized401No session cookie and no valid Bearer key.
insufficient_credits402Wallet can't cover urls × 4. Body carries required_credits, balance_credits and buy_credits_url. Nothing ran.
url_required / invalid_url400Pass `url` or `urls`; each must be a public http(s) address.
schema_required / schema_too_large400`schema` must be a JSON Schema object under 20,000 characters.
too_many_urls400At most 25 URLs per run.
too_many_running4295 runs already in flight for this account.
extraction_unavailable503The extractor is temporarily unavailable. Nothing was charged.
extraction_failed502The 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.

Mint a key and send the first URL.