An OPOS document is a single JSON file with five top-level fields:
version, metadata, equipment, ingredients, and protocol.
| Field | Type | Required | Description |
|---|---|---|---|
version |
string | ✅ | Spec version, e.g. "1.1.0" |
metadata |
object | ✅ | Recipe identity information |
equipment |
object | ✅ | Required hardware |
ingredients |
object | ✅ | Coffee and water amounts |
protocol |
array | ✅ | Ordered list of brewing steps |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | ✅ | Human-readable recipe name |
author |
string | ✅ | Recipe creator |
description |
string | ✅ | Short description of the technique |
compatibility |
array of strings | ✅ | Compatible dripper models (e.g. ["V60", "April"]) |
visualizer_hint |
string | ✅ | Rendering hint: "pulse_block" or "linear_ramp" |
visualizer_hint values:
| Value | Meaning |
|---|---|
pulse_block |
Discrete, evenly spaced pours (e.g. 4:6 method) — renders each pour as a separate block |
linear_ramp |
Continuous or ramped flow (e.g. single-pour) — renders as a smooth ramp |
| Field | Type | Required | Description |
|---|---|---|---|
dripper |
object | ✅ | Dripper details |
dripper.model |
string | ✅ | Dripper model name (e.g. "V60", "Kalita Wave", "Chemex") |
dripper.material |
string | ✅ | Dripper material: "Ceramic", "Plastic", "Glass", "Metal", "Paper" |
filter_type |
string | ✅ | Filter type: "Paper", "Metal", or "Cloth" |
| Field | Type | Required | Description |
|---|---|---|---|
coffee_g |
number | ✅ | Coffee dose in grams |
water_g |
number | ✅ | Total water in grams |
grind_setting |
string | ✅ | Grind descriptor: "Coarse", "Medium-Coarse", "Medium", "Medium-Fine", "Fine" |
The ratio water_g / coffee_g gives the brew ratio (e.g. 15 for a 1:15 ratio).
Each step in the protocol array is an object:
| Field | Type | Required | Description |
|---|---|---|---|
step |
integer | ✅ | Step number (1-indexed, sequential) |
action |
string | ✅ | Action type (see below) |
water_g |
number | ✅ for bloom/pour |
Water added in this step (grams) |
duration_s |
number | ✅ | Step duration in seconds |
notes |
string | ❌ | Human-readable brewing guidance |
action values:
| Action | Description | Adds water |
|---|---|---|
bloom |
Initial wetting and degassing of grounds | ✅ (water_g required) |
pour |
Add water to the brewer | ✅ (water_g required) |
wait |
Pause — no water, no agitation | ❌ |
swirl |
Gently swirl or spin the brewer | ❌ |
stir |
Stir the grounds (spoon, WDT tool, etc.) | ❌ |
The sum of all water_g values across all steps should equal ingredients.water_g.
OPOS follows Semantic Versioning:
| Change type | Version bump |
|---|---|
| Clarifications, no schema changes | Patch (1.1.x) |
Backwards-compatible additions (new optional fields, new action values) |
Minor (1.x.0) |
| Breaking changes (removed fields, changed semantics) | Major (x.0.0) |
- Minimal — only fields that are needed to brew and visualise a recipe
- Human-editable — no machine-generated IDs or base64 blobs
- Composable — the format does not mandate any specific brewing hardware; an implementation selects how to execute each action
- Extensible — new
actiontypes and metadata fields can be added in minor versions without breaking existing parsers
{
"version": "1.1.0",
"metadata": {
"name": "James Hoffmann V2",
"author": "James Hoffmann",
"description": "Better 1-Cup V60 technique. Bloom with 2–3× water, two pours with a brief rest, then a gentle swirl to level the bed before drawdown.",
"compatibility": ["V60"],
"visualizer_hint": "pulse_block"
},
"equipment": {
"dripper": { "model": "V60", "material": "Plastic" },
"filter_type": "Paper"
},
"ingredients": {
"coffee_g": 15.0,
"water_g": 250.0,
"grind_setting": "Medium-Fine"
},
"protocol": [
{ "step": 1, "action": "bloom", "water_g": 50, "duration_s": 45, "notes": "Saturate grounds with 2–3× dose weight, then swirl gently" },
{ "step": 2, "action": "pour", "water_g": 100, "duration_s": 30, "notes": "Pour to 150g total" },
{ "step": 3, "action": "wait", "duration_s": 10 },
{ "step": 4, "action": "pour", "water_g": 100, "duration_s": 30, "notes": "Pour to 250g total" },
{ "step": 5, "action": "swirl", "duration_s": 5, "notes": "Gentle swirl to flatten the bed" },
{ "step": 6, "action": "wait", "duration_s": 90, "notes": "Allow to drain completely" }
]
}CC0 1.0 Universal — see LICENSE.