+ ttyinv

AUTHORITATIVE MANUAL

One invoice grammar,
every surface reads it.

Connect over MCP, call the REST API, or run the same Rust engine locally with the CLI. This static guide documents public contracts.

QUICKSTART

Start with the skill and a revocable token.

  1. Download the ttyinv-invoice skill with its SHA-256 checksum, or inspect the Agents page.
  2. Sign in on the hosted app, create a named token, and store it locally as TTYINV_AGENT_TOKEN.
  3. Choose MCP or REST. Both create the same canonical ttyinv/v2 Markdown.
No agent operation needs a purchase. Agent tokens require GitHub authentication and nothing else. https://app.ttyinv.com/api/v1/invoices/render and render_invoice run the same Rust renderer as the CLI and the editor, and answer with HTML, PDF, or PNG bytes.

DOCUMENT GRAMMAR

Content first, configuration small.

Frontmatter carries required schema: ttyinv/v2 and these optional configuration keys. JSON and YAML use snake_case names.

Frontmatter keys
KeyAccepted valuesDefault
formatcode-comma-dot code-dot-comma code-space-comma code-indian code-plaincode-comma-dot
themeprintable paper-white graphite blueprint ledger-pad solarized-light parchment midnight nord gruvbox-darkprintable
fontgeist-mono cousine fira ibm-plex inconsolata jetbrains roboto source-code space ubuntugeist-mono
font-weightregular semiboldregular
densitycomfortable compactcomfortable
amount-in-wordstrue falsefalse
accentlowercase #rrggbb, or absenttheme accent
font-scaleinteger 100–140100
frame-insetinteger 30–6054

The body is an H1 title, a metadata list, the fixed ## Fromand ## Bill to parties, then ordinary ## sections that each hold exactly one body kind — a table or prose. The optional footer is ## Settlements, ## Payment,## Signature, in that order. Those five names are reserved, so an ordinary section cannot take one.

A directive belongs to the block below it and travels with that block when the section moves: <!-- ttyinv:gap-before none -->and its other levels (tight, standard,roomy), <!-- ttyinv:page-break-before -->, and <!-- ttyinv:summary-only -->. The argument follows the name after a space; there is no colon. Every image requires alt text. The editor renders image paths as bounded placeholders. Only an explicit agent render request can supply validated asset bytes.

Amounts use presets, not locales

Each preset prints the ISO 4217 code before the amount, so a reader of any language reads the same marker. Arithmetic runs on minor units with BigInt; no Intl locale is consulted.

code-comma-dotEUR 1,234.56
code-dot-commaEUR 1.234,56
code-space-commaEUR 1 234,56
code-indianINR 12,34,567.89
code-plainEUR 1234.56

DISCOVERY

Fetch the live map before you call an endpoint.

Fetch https://app.ttyinv.com/api/v1 first. Then fetch https://app.ttyinv.com/api/v1/invoices/schema for exact input and output schemas.

The discovery response lists service and contract versions, REST paths, MCP tools, authentication, operation availability, status meanings, and response headers.

curl --fail --silent --show-error "https://app.ttyinv.com/api/v1"

AUTHENTICATION

Bearer tokens are private and shown once.

Send Authorization: Bearer $TTYINV_AGENT_TOKEN on MCP and REST requests. Tokens expire, can be revoked immediately, and are stored by ttyinv only as SHA-256 digests.

  • Keep tokens in an environment variable or secret manager, not source, command history, URLs, chat, screenshots, or logs.
  • Never use verbose HTTP output or shell tracing around authenticated commands.
  • A 401 means the token is missing, expired, invalid, or revoked. Create a replacement.
  • A 429 means the authenticated request limiter rejected excess requests. A render queue does not use 429.

1 · MCP

Prefer MCP when your agent supports tools.

Use the Streamable HTTP endpoint at https://app.ttyinv.com/mcp. Send a Bearer token on every request.

Initialize with MCP initialize over POST. Discover tools with tools/list after initialization.

{
  "mcpServers": {
    "ttyinv": {
      "type": "http",
      "url": "https://app.ttyinv.com/mcp",
      "headers": { "Authorization": "Bearer $TTYINV_AGENT_TOKEN" }
    }
  }
}
create_invoiceCreate needs no purchase. Its input and output schemas come from the live schema.
validate_invoiceValidate needs no purchase. Its input and output schemas come from the live schema.
render_invoiceRender needs no purchase. It answers a manifest with the format, page count, dimensions, warnings, and one base64 file.

MCP returns JSON errors with the same status meanings as REST. Common statuses include 400, 401, 403, 413, 422, 429, 500, and 503.

2 · REST API

Use strict JSON over ordinary HTTP.

Discovery and authenticated REST operations use Cache-Control: no-store. The public schema uses Cache-Control: public, max-age=300, stale-while-revalidate=3600. Fetch discovery, then fetch the schema.

REST endpoint contracts
EndpointAuthentication and content typeInput and success outputPurchaseCommon failures
GET https://app.ttyinv.com/api/v1None. Server: Returns application/json.Input: none. Success: 200, discovery document.None.None expected.
GET https://app.ttyinv.com/api/v1/invoices/schemaNone. Server: Returns application/json.Input: none. Success: 200, schema envelope.None.None expected.
POST https://app.ttyinv.com/api/v1/invoicesBearer token. Client: Send application/json.Input: kind:"create" with nested draft. Success: 201, {created:{source,document,revision}}.None.400, 401, 403, 413, 415, 422, 429.
POST https://app.ttyinv.com/api/v1/invoices/validateBearer token. Client: Send application/json.Input: kind:"validate" with tagged source. Success: {validated:{revision,valid,document,diagnostics}}.None.400, 401, 403, 413, 415, 422, 429.
GET https://app.ttyinv.com/api/v1/invoices/renderBearer token. Server: Returns application/json.Input: none. Success: 200, render capabilities.None.401, 403, 429.
POST https://app.ttyinv.com/api/v1/invoices/renderBearer token. Client: Send application/json.Input: kind:"render" with tagged source and nested options. Success: {rendered:{source_revision,plan_digest,bytes,output_sha256,mime,extension,pages,width,height,warnings}}.None.400, 401, 403, 413, 415, 422, 429, 500, 503.

SCHEMA DISCOVERY

Fetch contracts; do not copy fields into agent prompts.

https://app.ttyinv.com/api/v1/invoices/schema is the authoritative JSON Schema envelope generated from the Rust command registry and shared by REST and MCP. Each operation entry carries its exact input and successful output schemas, and discovery reports the availability of every operation. Structured JSON and YAML accept null only where the Rust type is Option. Fetch the schema before composing requests. Do not copy field lists into prompts.

curl --fail --silent --show-error "https://app.ttyinv.com/api/v1/invoices/schema"

The CLI writes the underlying ttyinv/v2 document schema.

CREATE CONTRACT

Structured input returns source first.

This fabricated payload uses the Rust kind:"create" envelope and nested draft. Consult the live schema for required fields, limits, formats, and newly added capabilities.

{
  "kind": "create",
  "draft": {
    "config": {
      "schema": "ttyinv/v2",
      "format": "code-comma-dot",
      "theme": "printable",
      "font": "geist-mono",
      "font_weight": "regular",
      "density": "comfortable",
      "amount_in_words": false,
      "accent": null,
      "font_scale": 100,
      "frame_inset": 54
    },
    "title": "Fabricated services",
    "metadata": {
      "number": "INV-EXAMPLE-001",
      "kind": "standard",
      "issued": "2026-08-24",
      "due": "2026-09-07",
      "currency": "USD"
    },
    "from": { "name": "Northstar Example Studio", "email": "billing@example.com" },
    "bill_to": { "name": "Acme Example Client" },
    "ordinary_sections": [{
      "title": "Services",
      "body": {
        "headings": ["Description", "Amount (USD)"],
        "rows": [["Fabricated accessibility review", "125.00"]]
      }
    }]
  }
}
curl --fail-with-body --silent --show-error \
  --config <(cat <<EOF
header = "Authorization: Bearer $TTYINV_AGENT_TOKEN"
header = "Content-Type: application/json"
EOF
) --data-binary @invoice.json "https://app.ttyinv.com/api/v1/invoices" > created.json

The Rust success outcome is {created:{source,document,revision}}. The adapter builds a document and calls the same canonical serializer as the editor, so source is exactly the canonical form. Save it as the portable artifact.

SOURCE FIDELITY

Keep the authored invoice’s meaning intact.

  • Payable currency: when a table has several amount columns, exactly one qualified column matching metadata.currency is payable. Conversions are authored explicitly; ttyinv does not fetch exchange rates.
  • Authored summaries: rows named Subtotal, Total, or Grand Total remain visible and are excluded from generated totals.
  • Recap tables: set summary_only: true in a section’s directives object when a table repeats amounts from earlier sections. Its rows remain visible while the section is excluded from generated totals.
  • Deterministic page breaks: set page_break_before: true in a section’s directives object. The returned source emits the exact page-break marker.
  • Authored spacing: set gap in directives to none, tight, standard, or roomy. The marker stays attached to that section when it moves.

Fetch https://app.ttyinv.com/api/v1/invoices/schema before composing requests so REST and MCP stay aligned.

VALIDATE CONTRACT

Validation is explicit and read-only.

jq -n --rawfile source invoice.md '{
  kind:"validate",
  source:{markdown:$source}
}' | \
  curl --fail-with-body --silent --show-error \
  --config <(cat <<EOF
header = "Authorization: Bearer $TTYINV_AGENT_TOKEN"
header = "Content-Type: application/json"
EOF
) --data-binary @- "https://app.ttyinv.com/api/v1/invoices/validate"

The Rust success outcome is {validated:{revision,valid,document,diagnostics}}. Continue only when valid is true. When false, document is null and diagnostics identify the errors.

RENDER CONTRACT

One renderer, called over HTTP.

https://app.ttyinv.com/api/v1/invoices/render and the render_invoice tool run the same Rust core renderer that the CLI and the browser editor run, so one document with one set of options gives one file on every surface. Render authorization continues to use the existing invoices:create scope.

GET the same path for the live capability document: output formats, theme identifiers, font identifiers, density values, and every render limit. Read it instead of copying a list into your agent; discovery names the path in rest.operations.render.capabilities.path.

curl --fail-with-body --silent --show-error \
  --config <(cat <<EOF
header = "Authorization: Bearer $TTYINV_AGENT_TOKEN"
header = "Content-Type: application/json"
EOF
) --request GET "https://app.ttyinv.com/api/v1/invoices/render"

POST the Rust kind:"render" envelope. Put the tagged source and nested options object in the request. The options use font_weight, font_scale, frame_inset, and other snake_case keys.

jq -n --rawfile source invoice.md '{
  kind:"render",
  source:{markdown:$source},
  options:{
    format:"pdf",
    theme:"printable",
    font:"geist-mono",
    font_weight:"regular",
    density:"comfortable",
    accent:null,
    font_scale:100,
    frame_inset:54,
    png_scale:1
  }
}' | \
  curl --fail-with-body --silent --show-error \
  --config <(cat <<EOF
header = "Authorization: Bearer $TTYINV_AGENT_TOKEN"
header = "Content-Type: application/json"
EOF
) --data-binary @- "https://app.ttyinv.com/api/v1/invoices/render" | \
  jq -r '.rendered.bytes' | base64 --decode > invoice.pdf

The Rust success outcome is {rendered:{source_revision,plan_digest,bytes,output_sha256,mime,extension,pages,width,height,warnings}}. Native and WASM outcomes keep byte buffers raw. REST encodes bytes as base64 and output_sha256 as lowercase hexadecimal. The Rust and WASM output limit is 64 MiB.

The editor renders image paths as bounded placeholders. An explicit agent render request may include validated asset bytes under options.assets; the renderer never fetches an image. An oversize source or output answers 413. A full render queue or worker failure answers 503. The authenticated request limiter answers 429.

3 · CLI · LOCAL-FIRST

Use the CLI when contents must not cross the network.

The CLI is a Rust binary over the same core the browser runs as WebAssembly, so a document that validates in one validates identically in the other, and a document rendered by one matches the other. Build it from the public repository with make rust-release. There is no runtime to install.

ttyinv validate invoice.md --json
ttyinv sections invoice.md --json
ttyinv render invoice.md --format pdf --output invoice.pdf
ttyinv edit move-section invoice.md --from 3 --to 1 --stdout
ttyinv edit set-gap invoice.md --section 2 --gap roomy --check

Every CLI command

  • ttyinv validate FILE [--json] validates the document and reports diagnostics with their codes.
  • ttyinv schema [--output FILE] writes the ttyinv/v2 document schema.
  • ttyinv sections FILE [--json] lists the ordinary sections with position, title, body kind, and gap token.
  • ttyinv render FILE --format html|pdf|png [--output FILE|--stdout] writes the finished invoice with the shared renderer, and accepts --theme, --font, --font-weight, --density, --accent, --font-scale, --frame-inset, and --asset-base.
  • ttyinv edit move-section FILE --from N --to N moves one ordinary section, carrying its directives with it.
  • ttyinv edit set-gap FILE --section N --gap none|tight|standard|roomy sets the gap above one ordinary section.

Both edit commands accept --stdout to print instead of writing, --check to exit non-zero when the document would change, and --json for the full engine response. CLI section positions are one based. In-place edits write a sibling temporary file and rename. Status 5 means the render itself failed after the input and the options were accepted.

Read the complete CLI reference

PRIVACY & DATA FLOW

Choose the boundary deliberately.

Browser editorThe draft stays in this browser. It has no file input and does not load or upload font or signature files. Image paths render as bounded placeholders.
CLISource and assets stay on the local machine.
REST / MCPPayload crosses the network for transient processing. Only an explicit agent render request may include validated asset bytes. The payload is not stored or written to request logs.
Token metadataDigest, scope, expiry, usage count, and timestamps support authentication and abuse controls.

Use fabricated example.com identities for testing. Never commit a real invoice, customer record, signature, or credential.