Skip to main content
API reference

Paywalls

Generated HTTP reference for the 4 operations the published OpenAPI document describes under paywalls.

9 min read
View MarkdownEdit on GitHub

Paywalls

This page is generated from the published OpenAPI document. It is complete with respect to that document and says nothing about surfaces the document does not describe yet. See what is generated here for what that means.

Base URL: https://api.codespar.dev

Every operation below requires a Bearer token. See Authentication.

REST API for Gate, the x402 gateway. A paywall charges agents per call for an endpoint you own, settling in USDC over x402.

Base URL: https://api.codespar.dev

All endpoints require authentication. See Authentication. Endpoints marked admin require a bearer api key OR service auth with an x-codespar-user header for an account admin/owner. Paywalls are project-scoped.

Paywall object

FieldTypeDescription
idstringPaywall ID, pw_<...>
project_idstringOwning project
slugstringURL slug; global namespace. /^[a-z0-9_-]+$/
namestringDisplay name, max 128
upstream_urlstringWhere paid calls are proxied
pricestringUSDC per call, decimal (min "0.01")
price_atomicstringSame price in atomic USDC (6 dp)
currencystringUSDC
pricing_modelstringflat (default), tiered, dynamic, or metered
payto_kind"provisioned" | "byo"Where settlement lands
payto_addressstring | nullThe 0x address for byo
consumer_idstring | nullThe consumer whose wallet receives for provisioned
activebooleanPaused paywalls return 402 with no settlement
gateway_urlstringhttps://gw.codespar.dev/<slug>
created_atstringISO 8601

Create a paywall

POST /v1/paywalls (admin)

Body

{
  "slug": "market-data",
  "name": "Market data API",
  "upstream_url": "https://api.yourservice.com/quote",
  "price": "0.01",
  "currency": "USDC",
  "consumer_id": "your-consumer",
  "payto": { "kind": "provisioned" }
}
  • payto.kind: "provisioned" — settles to the CodeSpar wallet of consumer_id (required for this kind).
  • payto.kind: "byo" — settles to payto.address (a 0x EVM address).

Response201 Created with the paywall object.

Pricing models

pricing_model selects how the per-call price is resolved:

ValuePrice per callStatus
flatprice, the same on every call. The defaultLive
tieredprice steps down as the paywall's settled count crosses configured thresholdsLive
dynamicResolved per call from the paywall's dynamic ruleLive
meteredA ceiling is charged up front, then the actual cost is metered from the upstream response and the difference is refunded on-chain. See MeterBeta

Gateway protocol

The 402 challenge is delivered base64-encoded in the PAYMENT-REQUIRED response header (x402Version 2). The JSON body of the 402 is a human-readable hint: clients read the header, not the body.

Payment goes back in the PAYMENT-SIGNATURE request header. X-PAYMENT is still accepted for x402 v1 compatibility, and a v2 client may send both.

The settlement result comes back in the PAYMENT-RESPONSE response header (also mirrored to X-PAYMENT-RESPONSE for v1 clients). Both are listed in Access-Control-Expose-Headers, so a browser client can read them.

An optional Idempotency-Key request header makes a paywall call settle at most once, even across re-signed retries. A retry that arrives while the first is still in flight gets 409 idempotency_in_progress; a completed call replays the stored response with Idempotent-Replay: true.

List paywalls

GET /v1/paywalls

Response

{ "paywalls": [/* Paywall */] }

Get a paywall

GET /v1/paywalls/:id — the paywall object, or 404 paywall_not_found.

Update a paywall

PATCH /v1/paywalls/:id (admin)

Body (at least one field)

{ "name": "...", "upstream_url": "...", "price": "0.02", "active": false }

Set active: false to pause (callers get 402, nothing settles); true to resume.

Delete a paywall

DELETE /v1/paywalls/:id (admin)204 No Content. Paywalls are config, not audit data; settled receipts are retained independently.

Earnings

GET /v1/paywalls/:id/stats

Read-only aggregate over the paywall's settled payments.

Response

{
  "paywall_id": "pw_...",
  "slug": "market-data",
  "currency": "USDC",
  "settled_count": 128,
  "gross_atomic": "1280000",
  "gross": "1.28",
  "refunded_atomic": "0",
  "refunded": "0.00",
  "net_atomic": "1280000",
  "net": "1.28",
  "last_settled_at": "2026-07-10T21:04:11Z"
}

gross is what payers authorized, refunded is what went back to them, and net is what the paywall kept. On flat, tiered, and dynamic paywalls there are no refunds, so gross equals net. On a metered paywall gross is the sum of the signed ceilings and net is the actual metered revenue, so the two read as ceiling versus actual.

Errors

HTTPcodewhen
400invalid_bodybody failed validation
403forbiddennon-admin caller on an admin endpoint
404paywall_not_foundunknown id in this project
409slug_conflictthe slug is already taken

See also

  • Monetized MCP servers: per-tool pricing for an MCP server you already run, managed on /v1/mcp-servers and served at gw.codespar.dev/mcp/<slug>.
  • Getting discovered: the public manifest at gw.codespar.dev/.well-known/x402 that lists your active paywalls with their x402 terms.

Every operation, from the spec

Generated from the published OpenAPI document, so it never drifts from what the API actually serves. The section above is written by hand and carries what a schema cannot: the object model, field rules, and the order to call things in.

GET /v1/paywalls

List paywalls

Responses

StatusBodyDescription
200objectOK

Response 200

FieldTypeRequiredDescription
paywallsarray of objectyes

Example response

{
  "paywalls": [
    {
      "id": "obj_0000000000000000",
      "project_id": "prj_0000000000000000",
      "slug": "example",
      "name": "Example",
      "upstream_url": "https://example.com/hook",
      "price": "string",
      "price_atomic": "string",
      "currency": "BRL",
      "environment": "live",
      "payto_kind": "byo",
      "payto_address": "string",
      "pricing_model": "flat",
      "metered_config": {
        "basis": "string",
        "base_atomic": "string",
        "min_mult": 1,
        "max_mult": 1,
        "units_header": "string"
      },
      "active": true,
      "gateway_url": "https://example.com/hook",
      "created_at": "2026-01-15T12:00:00.000Z"
    }
  ]
}

Example request

curl -X GET https://api.codespar.dev/v1/paywalls \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

GET /v1/paywalls/{id}

Read one paywall

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
200objectOK
404objectNot Found. A paywall owned by another org or another project is indistinguishable from one that does not exist: the lookup filters on id AND org AND project in the same query, so a cross-tenant read is 404 and never 403.

Response 200

FieldTypeRequiredDescription
activebooleanyesThe gateway resolves ACTIVE rows only; an inactive paywall stops serving.
categorystring,nullyes
consumer_idstring,nullyesThe consumer whose wallet receives, for a provisioned payTo. Null for 'byo'.
created_atstring (date-time)yes
currencystringyes'USDC' on every row the create route writes; the column has no CHECK.
descriptionstring,nullyes
dynamic_price_urlstring,nullyesSeller price hook for 'dynamic': the gateway GETs it per request and quotes what it returns. It is fail-safe, not fail-closed: an unsafe, unreachable, non-2xx, malformed or timed-out hook falls back to price_atomic, so the gateway can always quote a price.
environment"live" | "test"yesInherited from the calling credential's project at create time.
gateway_urlstringyeshttps://gw.codespar.dev/\<slug\>, built from the slug on the way out. The id does not appear in it.
idstringyespw_ followed by a 16-character nanoid.
metered_configobject,nullyesConfig for 'metered'. Null for every other model.
methodsarray,nullyesHTTP methods this paywall fronts. Null means any method. A method that is not listed is refused by the gateway with 405 method_not_allowed and an Allow header, BEFORE any 402 challenge, so it cannot be paid for and then proxied.
namestringyes
payto_addressstringyesThe 0x address that receives settlement.
payto_kind"byo" | "provisioned"yes'byo': the seller supplied the receiving 0x USDC address. 'provisioned': CodeSpar derived and owns a CDP address for the tenant's consumer.
pricestringyesprice_atomic rendered as a decimal USDC string with trailing zeros trimmed ('1000' becomes '0.001'). Derived on the way out, not stored.
price_atomicstringyesThe stored price: USDC atomic units (6 decimals) as an integer string, to keep the x402 UintString convention exact.
pricing_model"flat" | "token" | "dynamic" | "time" | "per_unit" | "tiered" | "metered"yesThe full set a row may hold. Only 'flat', 'tiered', 'dynamic' and 'metered' can be created today; the create route refuses the other three with 400 pricing_model_unsupported rather than storing a model that would bill as flat.
pricing_tiersarray,nullyesAscending price curve for 'tiered'. Null for every other model.
project_idstringyes
slugstringyesThe public gateway key. UNIQUE GLOBALLY, not per project: the uniqueness index covers the slug alone, so a slug is first-come across all tenants and a taken one is refused with 400 slug_conflict on create.
upstream_urlstringyesOrigin the gateway proxies to once a call is paid for.

Example response

{
  "id": "obj_0000000000000000",
  "project_id": "prj_0000000000000000",
  "slug": "example",
  "name": "Example",
  "upstream_url": "https://example.com/hook",
  "price": "string",
  "price_atomic": "string",
  "currency": "BRL",
  "environment": "live",
  "payto_kind": "byo",
  "payto_address": "string",
  "pricing_model": "flat",
  "metered_config": {
    "basis": "string",
    "base_atomic": "string",
    "min_mult": 1,
    "max_mult": 1,
    "units_header": "string"
  },
  "active": true,
  "gateway_url": "https://example.com/hook",
  "created_at": "2026-01-15T12:00:00.000Z"
}

Example request

curl -X GET https://api.codespar.dev/v1/paywalls/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

DELETE /v1/paywalls/{id}

Delete a paywall

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
204No Content
403objectForbidden. The acting user does not satisfy the org-role guard in front of this route. With an OAuth access token this is the live behaviour when x-codespar-user is absent or names a user below admin; with an API key it appears only where the deployment enforces the same guard.
404objectNot Found. A paywall owned by another org or another project is indistinguishable from one that does not exist: the lookup filters on id AND org AND project in the same query, so a cross-tenant read is 404 and never 403.

Example request

curl -X DELETE https://api.codespar.dev/v1/paywalls/{id} \
  -H "Authorization: Bearer $CODESPAR_API_KEY"

GET /v1/paywalls/{id}/stats

Earnings for one paywall

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
200objectOK
404objectNot Found. A paywall owned by another org or another project is indistinguishable from one that does not exist: the lookup filters on id AND org AND project in the same query, so a cross-tenant read is 404 and never 403.

Response 200

FieldTypeRequiredDescription
currencystringyes
grossstringyesgross_atomic as a decimal USDC string.
gross_atomicstringyesSum of what payers authorized, in USDC atomic units.
last_settled_atstring,null (date-time)yesMost recent settlement, UTC. Null when nothing has settled.
netstringyesnet_atomic as a decimal USDC string.
net_atomicstringyesgross_atomic minus refunded_atomic, clamped at zero rather than allowed to go negative. Flat, tiered and dynamic paywalls have no refunds, so net equals gross there; on the metered lane the payer signs a ceiling and the difference goes back on-chain, so gross alone would report money the seller does not keep.
paywall_idstringyes
refundedstringyesrefunded_atomic as a decimal USDC string.
refunded_atomicstringyesSum of the metered refunds that are owed or already gone: attempts in status 'claimed', 'sent' or 'confirmed'. The refund lane writes five statuses and only these three are subtracted; the other two, 'waived' (below the dust floor, so no transfer is made) and 'failed' (the transfer or its transaction reverted), leave the money with the seller.
settled_countintegeryesSettled gateway calls attributed to this paywall.
slugstringyes

Example response

{
  "paywall_id": "paywall_0000000000000000",
  "slug": "example",
  "currency": "BRL",
  "settled_count": 1,
  "gross_atomic": "string",
  "gross": "string",
  "refunded_atomic": "string",
  "refunded": "string",
  "net_atomic": "string",
  "net": "string"
}

Example request

curl -X GET https://api.codespar.dev/v1/paywalls/{id}/stats \
  -H "Authorization: Bearer $CODESPAR_API_KEY"
Paywalls | CodeSpar