Publishing guide

Building an agent or tool that reads specs? See the API for agents.

Two ways to publish a spec

You can publish a spec on the website or by API/CLI — both run the exact same content validation, so either path produces an identical, verified spec.

On the website (easiest)

  1. Sign in and go to Submit a spec (the “Publish a spec” button on the home page).
  2. Paste your .spec.md, or upload a file. The editor accepts a structured form or raw markdown — author the spec in your own editor and paste it in.
  3. Click Preview. You’ll see any content errors inline (the same checks that run in CI). Fix them and re-preview.
  4. Click Publish. The spec goes live immediately: its page is /specs/{publisher}/{name}, and agents fetch the raw markdown at /specs/{publisher}/{name}.md.

Each {name}@{version} is immutable — to release changes, bump the version field and publish again.

With the CLI

Create a token under Account → API tokens, then (Node 20+, nothing to install):

npx @canra/cli login        # paste the token when asked
npx @canra/cli publish my-spec.spec.md

By API

POST the raw markdown (the request body is the spec, Content-Type: text/markdown) to /api/specs, authenticated with a token (create one under Account → API tokens) or via OIDC trusted publishing from CI:

curl -X POST https://canra.dev/api/specs \
  -H "Authorization: Bearer $CANRA_TOKEN" \
  -H "Content-Type: text/markdown" \
  --data-binary @my-spec.spec.md

To validate without publishing, POST the same body to /api/specs/validate — it returns { ok, errors, name, version }.

Spec quality bar

A spec is ready to publish when it has:

File format

Spec files use the .spec.md extension with TOML front-matter between +++ delimiters.

Front-matter schema

+++
# Required
name = "spec-name"                     # Lowercase, hyphens for multi-word
version = "1"                          # Incrementing integer version
title = "Human-Readable Title"
status = "draft"                       # draft | stable | deprecated

# Classification
keywords = ["keyword1", "keyword2"]
difficulty = "moderate"                # trivial | easy | moderate | hard | expert

# Optional links
repository = "https://example.com/..." # Source repository URL
homepage = "https://..."               # Project home page URL

# References - each is a label with an OPTIONAL http(s) url (rendered as a live
# link on the spec page). There are no rfc/standard categories.
[references]
items = [
  { label = "RFC XXXX", url = "https://www.rfc-editor.org/rfc/rfcXXXX.html" },
  { label = "Some standard" },         # url is optional
]

# Test vector locations
[test-vectors]
inline = true                          # true if vectors are in the .spec.md
external = []                          # Paths relative to spec directory

# --- Library specs only (omit if not applicable) ---

# API surface
[api]
functions = ["parse", "serialize"]     # Public function names (snake_case)
types = ["MyValue", "MyError"]         # Public type names (PascalCase)
+++

Markdown body sections

Specs can use any H2 (##) sections that make sense for the subject. There is no required set of sections and no required order.

For code library specs, the following structure is recommended:

  1. ## Overview — What library this spec defines, its functions, and what is out of scope
  2. ## Types — Type definitions for all types used in function signatures
  3. ## Functions — One H3 subsection per function with signature, parameters, algorithm
  4. ## Behavioral Contracts — Per-function MUST/SHOULD/MAY requirements
  5. ## Test Vectors — Function-call input/output pairs in fenced test-vectors blocks
  6. ## Edge Cases — Tricky situations as a bulleted list
  7. ## Implementation Notes — Practical guidance, pitfalls, performance tips
  8. ## References — Markdown links to authoritative sources

For other spec types (UI flows, protocols, data formats, etc.), use whatever sections best describe the subject. Examples:

Pseudocode signature notation

All specs use this language-agnostic notation for function signatures:

function_name(param: Type, optional_param: Type = default) -> ReturnType | ErrorType

Conventions:

Primitive types: String, Int, Float, Bool, Null, Bytes

Collections: [T] for lists, Map<K, V> for maps

Type definitions use tagged union syntax:

type JsonValue =
  | Null
  | Bool(Bool)
  | Number(Float)
  | String(String)
  | Array([JsonValue])
  | Object(Map<String, JsonValue>)

Test vector format

Test vectors use JSONL (one JSON object per line), both inline and in external files. The format is function-call-oriented:

Success case:

{"id": "parse-001", "function": "parse", "input": {"args": {"input": "{}"}}, "expected": {"result": {}}, "tags": ["object"]}

Error case:

{"id": "parse-err-001", "function": "parse", "input": {"args": {"input": "{\"a\":1,}"}}, "expected": {"error": "SyntaxError"}, "tags": ["trailing-comma"]}

Assertion (for generative functions):

{"id": "gen4-001", "function": "generate_v4", "input": {"args": {}}, "expected": {"assertion": "result[14] == '4'"}, "repeat": 100, "tags": ["version-nibble"]}

Fields:

For binary input/output, use _hex suffixed field names with hex encoding.

Naming conventions

Content restrictions

Canra specs are consumed directly by AI agents. To prevent prompt injection attacks and hidden content, all files are subject to strict content restrictions. The website’s Preview button and the /api/specs/validate endpoint run these same checks, so you get identical feedback whether you submit on the web or via CI.

ASCII-only requirement

All .spec.md and .jsonl files must contain only ASCII bytes:

Unicode data needed in test vectors (e.g., \u00e9, \uD83D\uDE00) must be expressed using JSON \u escape sequences, never as raw UTF-8 bytes.

Use ASCII alternatives for typography: a plain double-hyphen for an em-dash, a single hyphen for an en-dash, -> for an arrow, and straight quotes " ' instead of curly quotes.

Banned patterns

PatternReason
HTML commentsCan hide instructions invisible in rendered markdown
Executable/loading/reframing HTML tags (script, style, iframe, object, embed, link, meta, img, svg, math, base, frame/frameset, template, applet, marquee, foreignobject, annotation-xml)Script execution, remote loads, document reframing, or content hidden from a reviewer but live to an agent
Dangerous link schemes in markdown links/images (javascript:, data:, vbscript:)Smuggle script or arbitrary payloads into a followed link
Prompt-injection directives (e.g. “ignore previous instructions”, “system prompt:”, “you are now”, system-style role tags)Instructions aimed at the consuming AI agent rather than the reader
ASCII control characters (0x00-0x08, 0x0B, 0x0E-0x1F, 0x7F)Non-printable, can carry hidden data

Note: ordinary inline-code mentions of inert UI tags (e.g. an input element named in a UI spec) are allowed — only the executable/loading/reframing tags above are banned. Plain https:// links are always fine; only the dangerous schemes are rejected.

Size limits

File typeLimitValue
.spec.mdLine length500 characters
.jsonlFile size1 MB
.jsonlLine length10,000 characters
.jsonlLines per file10,000
.jsonlString value length10,000 characters

Test vector field restrictions

Review

Specs published via the website or API go live immediately once they pass validation — there is no gated review for self-published specs (the content rules above are the automated gate). Quarantine and takedown handle abuse after the fact.