CanPUFF canpuff.org

Open standard · v1.0-rc · July 2026

A file format for personal cannabis records.

CanPUFF documents the data an individual creates when they track what they consume, what they have, where they got it, and how it affected them. It is a format for people, not businesses — and it is designed to outlive any application, including the ones its authors write.

Think “RSS for personal cannabis data”: plain files you own, readable in any text editor, portable between apps, with no account, no API key, and no company that can take them away.
Round-tripped
1,752 records
Attachments
239 photos
Implementations
2
Fields lost
0

The gap

Every part of the supply chain has a data standard. The person at the end of it has none.

Seed-to-sale systems, dispensary point-of-sale, and regulatory traceability are all standardised and interoperable. Meanwhile every consumer tracking app keeps its records in an undocumented internal database — so the data dies with the app, and years of personal history go with it.

CanPUFF is the missing half: a documented, implementable format for the consumer's own records, published under CC0, with conformance fixtures so a second implementation can prove it agrees with the first.

The documents

What is specified, and how firm it is.

canpuff-v1.md

The vault layout, the journal (JSONL events), the catalog (Markdown + YAML frontmatter cards), attachments, identifiers, units, import merging, and the extension mechanism.

Frozen · 12 sections

canpuff-sealed-v1.md

End-to-end encryption with age v1, a key hierarchy rooted in a BIP39 mnemonic, and a synchronisation protocol that works against a dumb file server which never sees plaintext.

Under active development

mapping-pufftab-ios.md

Field by field, lossless, both directions: how the first implementation's 13-entity SwiftData model becomes CanPUFF and comes back unchanged.

Conformance contract

JSON Schemas

1.0-rc
schemas/v1/<type>.json

JSON Schema 2020-12 for every object type, served at the canonical $id URLs. Normative for the JSON form.

11 schemas

Examples

1.0-rc
examples/

A complete example plain vault, plus one standalone JSON object per type that validates against the schemas.

Copy-paste starting points

fixtures/

Edge-heavy valid vaults and deliberately broken ones, each with the reference validator's expected verdict, plus a runner. This is what makes a third-party implementation testable.

Distributed via git, not the site

tools/

validate-vault.py for conformance and diff-vaults.py for semantic equivalence of two vaults. Standalone, no dependencies; both accept a directory or a .canpuff.zip.

Round-trip verification

At a glance

The whole format is a directory you can read without us.

Two tiers: a private journal of events, and a shareable catalog of documents. Binary files are content-addressed by SHA-256. Nothing else.

The plain vault · Core §3
<vault>/
  manifest.json                    REQUIRED  vault identity & format version
  journal/
    <YYYY>/<MM>.jsonl              journal events, one JSON object per line
  catalog/
    supplies/<id>.md               shops · chains · brands · producers
    methods/<id>.md                tax-rates · terpenes
  attachments/
    <hh>/<sha256>.<ext>            content-addressed binary files
  apps/
    <app-id>/...                   application-private data, carried through intact

Readers must tolerate unknown files and directories, so the format can grow without breaking anything.

A journal event · journal/2026/07.jsonl
{
  "type": "consumption",
  "version": 1,
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "at": "2026-07-09T16:20:00-07:00",
  "supply": "9f3c1a2e-7b41-4c8e-9d2a-51f08c77b3aa",
  "amount": { "grams": 0.35 },
  "shared": { "people": 2, "gift": false },
  "effects": { "rating": 7 },
  "notes": "Paired with a movie."
}

Pretty-printed here; on disk it is one line. Private by default — journal data never leaves a trusted device unencrypted.

A catalog card · catalog/supplies/9f3c….md
---
type: supply
version: 1
id: 9f3c1a2e-7b41-4c8e-9d2a-51f08c77b3aa
name: Pink Kush
variety: indica
thc: 22.5
terpenes:
  - { name: Myrcene, percentage: 0.8 }
grams: 2.1
cost: 29.99
currency: CAD
---

Dense buds, gassy. Better in the bong than rolled.
Second time buying — first batch was drier.

Opens in any text editor, and natively in Obsidian. Contains nothing about the user's behaviour, so it is safe to share.

Design principles

Seven decisions that explain most of the spec.

  1. File over app

    The format is the product. Applications — including PuffTab — are replaceable CRUD interfaces over it.

  2. Journal / catalog split

    Events are private by default and never leave the user's control unencrypted. Product, shop and brand records contain nothing personal and are inherently shareable.

  3. Human-legible where a human might look

    Catalog cards are Markdown with YAML frontmatter. The journal is JSONL — still greppable, built for machines.

  4. Machine-exact where machines interoperate

    The canonical data model is JSON, specified by JSON Schema. Interchange, QR payloads and validation always use the JSON form.

  5. Private by architecture, not policy

    Consumption records are health data and, in some jurisdictions, self-incriminating. The format treats that as a design input, not a disclaimer.

  6. Extensible without forking

    Unknown fields are ignored, exactly one blessed extension mechanism exists, and every object carries type and version.

  7. Boring on purpose

    UUIDs, RFC 3339, SHA-256, JSON Schema, age encryption. No invented primitives, no clever tricks to regret later.

Status & governance

Frozen where it counts, open where it should be.

The core format is a v1.0 release candidate: frozen, standing on a five-lens adversarial review with 83 findings applied, and a proven lossless round-trip of real data between two independent implementations — the PuffTab iOS app and the PuffTab web app. Breaking changes now require a demonstrated interoperability failure; additive proposals target v1.1 under the versioning rules in Core §10. The sealed profile remains a draft, and v1.0 final follows its validation.

Spec text and schemas are published under CC0-1.0, with an OWFa 1.0 patent non-assertion covering implementations. Feedback, implementations and proposals are all welcome — the extension mechanism is the intended first stop for a new need, and fields that prove themselves in ext are candidates for the next minor version.