API reference

Docs

One endpoint does the work. Everything else — reading, listing, sharing, template samples — is free, because it costs no upstream work.

Quickstart

Sign in with your Ounie account, mint a key at /dashboard/api-keys, and post some markdown. A render takes about a second and costs 3 credits.

curl -X POST https://paperwork.ounie.com/api/documents \
  -H "Authorization: Bearer ppw_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "markdown": "# Q3 review\n\nThroughput rose **18%**.",
    "template": "report",
    "brand": { "name": "Northwind", "accent": "#b45309" },
    "share": true
  }'
{
  "ok": true,
  "document": {
    "id": "3f9a21c4-…",
    "title": "Q3 review",
    "template": "report",
    "pages": 2,
    "words": 412,
    "bytes": 48210,
    "warnings": [],
    "shared": true,
    "share_url": "https://paperwork.ounie.com/d/3f9a21c4-…",
    "pdf_url": "https://…signed…"
  },
  "credits_spent": 3
}

Render a document

POST/api/documents3 credits

Send markdown or blocks — one of the two is required. Everything else is optional.

FieldTypeMeaning
markdownstringGitHub-flavoured markdown. Headings, lists, tables, quotes, fenced code. Raw HTML is dropped and counted. A line containing only \pagebreak forces a new page.
blocksBlock[]Structured blocks instead of markdown, when you want exact control. See the block schema below.
template"report" | "onepager" | "memo" | "invoice" | "letter"Default report.
titlestringIf omitted and the body opens on an H1, that H1 becomes the title and is removed from the body.
subtitlestringStandfirst under the title.
brand.namestringWordmark on the cover and running head.
brand.accentstringHex, e.g. #b45309. Darkened automatically if it would not reach 4.5:1 on paper.
brand.logoUrlstringhttps PNG or JPEG, max 2MB. If it cannot be fetched the render still succeeds and says so in warnings.
pageSize"a4" | "letter"Default a4.
sharebooleanAlso publish a hosted web page at /d/<id>. Free and revocable.

warnings in the response is advisory, never fatal: it reports dropped HTML, an unreachable logo, or input clipped by a limit. A document that parses to nothing at all is a 422 and costs nothing.

Block schema

Every text field accepts inline markdown, so **bold**, *italic*, `code` and links work anywhere without hand-building a tree.

{ "type": "heading",   "level": 1-4, "text": "…" }
{ "type": "paragraph", "text": "…" }
{ "type": "list",      "ordered": false, "start": 1, "items": ["…"] }
{ "type": "quote",     "text": "…" }
{ "type": "code",      "language": "json", "text": "…" }
{ "type": "table",     "columns": ["…"], "rows": [["…"]],
                       "align": ["left"|"center"|"right"] }
{ "type": "keyvalue",  "items": [{ "label": "To", "value": "…" }] }
{ "type": "callout",   "tone": "neutral"|"accent", "title": "…", "text": "…" }
{ "type": "signature", "name": "…", "role": "…" }
{ "type": "rule" }
{ "type": "pagebreak" }
{ "type": "spacer",    "size": "sm"|"md"|"lg" }

Two blocks are template-aware. A keyvalue block at the very top of a memo or invoice is hoisted into the letterhead — that is what makes the TO/FROM/DATE/RE grid and the invoice meta panel. Anywhere else it is rendered inline as a ruled definition list.

A letter with no signature block gets one automatically, under the brand name. It is the only place a template adds content, and only because the form requires it.

Templates

  • reportReportsample PDF →

    A cover page, running heads, folio numbers and a serif text face. For anything long enough to need finding your place again.

  • onepagerOne-pagersample PDF →

    One dense sheet with a rule-topped masthead. Sans text, generous leading, built to be read standing up.

  • The classic TO / FROM / DATE / RE block over a ruled body. Short, dated, filed.

  • invoiceInvoicesample PDF →

    Brand block, bill-to, a real ruled table and a totals callout that stays with its table.

  • letterLettersample PDF →

    Letterhead, date, recipient block, salutation, and a signature rule at the end.

Sample PDFs render live from the same engine and are free — they use fixed content of ours, not yours, so nothing upstream is spent.

Reads & sharing

All of these are free, permanently. You already paid for the render.

GET/api/documents?limit=20free
GET/api/documents/{id}free
DELETE/api/documents/{id}free
POST/api/documents/{id}/sharefree
DELETE/api/documents/{id}/sharefree

Every read returns a freshly signed pdf_url, valid for 24 hours. Signatures are never stored, so a link you copied yesterday expires while the document stays available.

A shared document is served at /d/<id> as a real web page, with /d/<id>/pdf minting a five-minute signature per click. Unpublishing therefore kills links that are already in the wild, which a long-lived signed URL could not.

MCP server

Streamable HTTP at https://paperwork.ounie.com/api/mcp. Works with Claude, Cursor, the AI SDK and the Ounie AI Team.

{
  "mcpServers": {
    "paperwork": {
      "type": "http",
      "url": "https://paperwork.ounie.com/api/mcp",
      "headers": { "Authorization": "Bearer ppw_live_…" }
    }
  }
}

Some hosts — the Ounie AI Team among them — cannot set a static header on a manual HTTP entry. The key rides the URL instead:

https://paperwork.ounie.com/api/mcp?api_key=ppw_live_…
ToolCost
render_document3 credits
get_documentfree
list_documentsfree
share_documentfree
delete_documentfree
list_templatesfree · no auth
get_credit_balancefree
get_pricingfree · no auth
whoamifree

Credit safety is server-side: a call that cannot be paid for is refused with insufficient_credits before any work happens, so an agent driving this in a loop can never overdraw. Credits live on ounie.com.

The app also accepts your ounie.com ounie_live_master key when you have enabled “Use across Ounie apps” in your ounie.com settings — one key for the whole fleet.

x402 — pay per call, no account

POST/api/x402/render$0.036 USDC

Same body as /api/documents. USDC on Base at 36000 atomic units. Two rules govern the order of operations, and both exist because an on-chain settlement has no refund:

  1. 1.A bad request is refused free. An unsupported template, an empty body, or content that can never produce a page returns 4xx with no price quoted at all — you are never asked to sign an authorization for work that cannot be delivered.
  2. 2.Verify, render, then settle. The payment is verified before the work and settled only once a PDF exists. If the render fails, nothing moves on chain.
POST https://paperwork.ounie.com/api/x402/render
→ 402 {
    "x402Version": 1,
    "accepts": [{
      "scheme": "exact",
      "network": "base",
      "maxAmountRequired": "36000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x…",
      "extra": { "name": "USD Coin", "version": "2" }
    }]
  }

# sign, then retry
POST https://paperwork.ounie.com/api/x402/render
     X-Payment: <base64 payload>
→ 200 { "document": { "pdf_url": "…" }, "payment": { "tx_hash": "0x…" } }

An x402 document belongs to no account, so there is no dashboard from which anyone could delete it. Those files are kept for 30 days and then removed. Download the PDF; do not treat the link as permanent storage.

Errors

CodeStatusMeaning
unauthorized401No credential. Send a session cookie, an Authorization: Bearer ppw_live_… , or ?api_key=.
invalid_payload400The body failed validation. `detail` names the offending fields.
invalid_request400x402 only: the same validation, refused before any price is quoted.
empty_document422Nothing renderable after parsing — usually an HTML paste. Nothing is charged.
insufficient_credits402Includes required_credits, balance_credits and buy_credits_url. Nothing is charged.
not_found404No such document for this owner.
storage_unavailable503Rendering is not configured on this deployment. Nothing is charged.
render_failed500The typesetter refused the document. Nothing is charged.
payment_invalid402x402 only: the X-Payment header did not verify.
payment_settlement_failed402x402 only: verified but the facilitator refused to settle. The document is withheld.

Every non-2xx that can involve money says so explicitly. Credits are reserved before the work and restored to the exact pools they came from on any failure — there is no path in which a failed render leaves you short.

Limits

LimitValue
Markdown length400,000 characters
Blocks per document3,000
List items500
Table rows × columns800 × 12
Code block length40,000 characters
Logo size2 MB, PNG or JPEG, https only
List endpoint100 documents per call
Active API keys5 per account

Exceeding a limit clips the input and reports truncated in warnings rather than failing the render — a document with the last table row missing is more useful than a 400.

Something unclear or missing? Tell us.