{
  "openapi": "3.1.0",
  "info": {
    "title": "laserbrain",
    "version": "2026-08-27",
    "summary": "A runtime goal-alignment harness that asserts any user-directed context in order to interrupt agentic context misalignment during a task.",
    "description": "An agent calls this API each step with the goal it is currently pursuing. laserbrain scores that goal against a **ground state frozen when the run began** and returns a verdict. The reference does not move with the work — only a turn from the user moves it — which is the property the measurement depends on and the reason an agent cannot perform this check on itself.\n\n### Two ways in\n\n`POST /v1/check` is **stateless and needs no key**. You hold the ground and pass it with every call; nothing is stored but a rate-limit counter. Use it to try the detector, and for integrations that already keep their own state.\n\n`POST /v1/drift` is **stateful and needs a key**. laserbrain remembers the ground for a run, so an agent in a loop sends one step at a time. Run history, retention, webhook alerts and the account store hang off this route.\n\n### Honesty about the detector\n\nMeasured precision on `goal-drift` is **14.6%**, and the one controlled study run on laserbrain returned a null result. Those figures are published at https://phronesis.world/laserbrain/evidence and are not footnotes to this document — they are the reason the free path exists, so you can measure it on your own work rather than take a number on trust.\n\n### The field is not the detector\n\n`/v1/signal`, `/v1/history` and `/v1/hear` are the laserfield weather system laserbrain grew out of. They are documented here because they are reachable, not because they are the product.\n\nAdministrative routes under `/v1/admin/*` carry a separate credential and are deliberately absent from this document.",
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    },
    "contact": {
      "name": "phronesis",
      "url": "https://phronesis.world/laserbrain"
    }
  },
  "servers": [
    {
      "url": "https://api.phronesis.world",
      "description": "production"
    },
    {
      "url": "https://laserbrain-mcp.degibug.workers.dev",
      "description": "the original host. Still serving, and named by the published SDKs and the MCP registry entry, so it is not going away."
    }
  ],
  "externalDocs": {
    "description": "Reference, quickstart and the evidence ladder",
    "url": "https://phronesis.world/laserbrain"
  },
  "tags": [
    {
      "name": "detector",
      "description": "Scoring a step against the goal a run started with."
    },
    {
      "name": "account",
      "description": "Keys, tiers, usage and cost."
    },
    {
      "name": "operating",
      "description": "Webhook alerts, digests and team seats."
    },
    {
      "name": "field",
      "description": "The laserfield weather system. NOT the detector."
    },
    {
      "name": "team",
      "description": "Team seats and the fleet view."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "keyQuery": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Authorization: Bearer lb_live_… — a free key is issued to a GitHub account at https://phronesis.world/laserbrain. POST /v1/check needs none."
      },
      "keyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key",
        "description": "The same key as a query parameter, accepted wherever the header is. Prefer the header: a query string lands in logs and referrers."
      }
    }
  },
  "paths": {
    "/v1/alerts": {
      "get": {
        "summary": "Read the drift-alert webhook config",
        "description": "Returns the stored alert config for the calling key (KV `alert:<id>`), or null if none is set, plus the list of drift modes and their default severities. Not tier-gated: a `ground` key may read this even though it cannot POST one. src/api.ts:1330-1335.",
        "operationId": "getAlerts",
        "responses": {
          "200": {
            "description": "Current config (null when unset) and the drift-mode catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "alert",
                    "modes"
                  ],
                  "properties": {
                    "alert": {
                      "description": "The stored config, returned verbatim from KV, or null when no webhook is configured.",
                      "type": [
                        "object",
                        "null"
                      ],
                      "required": [
                        "url",
                        "min_phi",
                        "created"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Default webhook URL."
                        },
                        "min_phi": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 1,
                          "description": "Account-wide floor; events below it are not delivered."
                        },
                        "created": {
                          "type": "string",
                          "format": "date-time",
                          "description": "ISO timestamp of when the config was last written."
                        },
                        "modes": {
                          "type": "object",
                          "description": "Optional. Present only if per-mode overrides were accepted on the last POST. Keys are drift-mode names.",
                          "additionalProperties": {
                            "type": "object",
                            "required": [
                              "on",
                              "severity"
                            ],
                            "properties": {
                              "on": {
                                "type": "boolean"
                              },
                              "severity": {
                                "type": "string",
                                "enum": [
                                  "critical",
                                  "warn",
                                  "info"
                                ]
                              },
                              "url": {
                                "type": "string",
                                "format": "uri",
                                "description": "Optional per-mode webhook override."
                              },
                              "min_phi": {
                                "type": "number",
                                "minimum": 0,
                                "maximum": 1,
                                "description": "Optional per-mode floor; overrides the account-wide min_phi."
                              }
                            }
                          }
                        }
                      }
                    },
                    "modes": {
                      "type": "array",
                      "description": "The five recognised drift modes with their default severity.",
                      "items": {
                        "type": "object",
                        "required": [
                          "reason",
                          "default_severity"
                        ],
                        "properties": {
                          "reason": {
                            "type": "string",
                            "enum": [
                              "ungrammatical",
                              "goal-drift",
                              "self-report:stuck",
                              "self-report:circling",
                              "stalled"
                            ]
                          },
                          "default_severity": {
                            "type": "string",
                            "enum": [
                              "critical",
                              "warn",
                              "info"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present on no_key only."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      },
      "post": {
        "summary": "Set the drift-alert webhook",
        "description": "Writes KV `alert:<id>`, replacing any previous config wholesale (fields omitted from the body are reset to their defaults, not merged). A missing or unparseable JSON body is treated as `{}` and therefore fails webhook validation with 400.\n\nWHAT LASERBRAIN POSTS TO YOUR WEBHOOK. fireAlert() (src/api.ts:449-476) is called from the /v1/drift and /v1/dialogue handlers when a step is judged drifting, from the escalation handler, and from /v1/alerts/test. It sends `POST <url>` with `content-type: application/json`, `user-agent: laserbrain-alerts/1`, and a 5-second timeout. The body shape depends on the webhook host (webhookKind, src/api.ts:423-429):\n\n- `hooks.slack.com` -> `{\"text\": string}`\n- `discord.com`, `discordapp.com`, `*.discord.com` -> `{\"content\": string}`\n  (both carry the same rendered line: icon, then `laserbrain · *<reason>* (<severity>) · Φ <phi to 2dp>`, then the goal on its own line if present, then ``run `<run_id>` ``, then the advice if present; icon is 🔴 critical / 🟠 warn / 🔵 info; Discord bolds with `**`, Slack with `*`)\n- any other host -> the full structured event, unchanged:\n  `{\"event\": \"drift\", \"account\": string, \"run_id\": string, \"goal\": string|null, \"reason\": string, \"severity\": \"critical\"|\"warn\"|\"info\", \"phi\": number, \"advice\": string, \"ts\": string (ISO 8601)}`\n\nDelivery is suppressed when the matching mode has `on: false`, or when `phi` is below that mode's `min_phi` (falling back to the account-wide `min_phi`, then 0). The per-mode `url` is used when set and still valid, otherwise the top-level `url`. Every attempt, delivered or suppressed, is appended to the ring buffer readable at GET /v1/alerts/log. src/api.ts:1336-1345.",
        "operationId": "setAlerts",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Required. Coerced with String() and trimmed. Must be https:// on a public host: loopback, 10/172.16-31/192.168/169.254/0.x, ::1, fd*/fe80*, and .localhost/.internal/.local or dotless hostnames are all rejected (validWebhook, src/api.ts:406-417)."
                  },
                  "min_phi": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "default": 0,
                    "description": "Optional. Coerced with `Number(x) || 0` then clamped to 0..1, so a non-numeric or absent value stores 0."
                  },
                  "modes": {
                    "type": "object",
                    "description": "Optional per-mode overrides (sanitizeModes, src/api.ts:388-401). Only the five known keys are read — ungrammatical, goal-drift, self-report:stuck, self-report:circling, stalled — and anything else is discarded without error. A key whose value is not an object is skipped. If nothing survives, `modes` is omitted from the stored config entirely.",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "on": {
                          "type": "boolean",
                          "default": true,
                          "description": "Stored as `v.on !== false`, so any value other than literal false enables the mode."
                        },
                        "severity": {
                          "type": "string",
                          "enum": [
                            "critical",
                            "warn",
                            "info"
                          ],
                          "description": "Anything outside the enum falls back to that mode's default severity."
                        },
                        "url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Optional per-mode webhook. Dropped silently if blank or if it fails validWebhook."
                        },
                        "min_phi": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 1,
                          "description": "Optional. Stored only when non-null; coerced with `Number(x) || 0` then clamped."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Config stored. `alert` is the exact object written to KV.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "alert"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "alert": {
                      "type": "object",
                      "required": [
                        "url",
                        "min_phi",
                        "created"
                      ],
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "min_phi": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 1
                        },
                        "created": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "modes": {
                          "type": "object",
                          "description": "Optional. Spread in only when sanitizeModes returned at least one valid mode.",
                          "additionalProperties": {
                            "type": "object",
                            "required": [
                              "on",
                              "severity"
                            ],
                            "properties": {
                              "on": {
                                "type": "boolean"
                              },
                              "severity": {
                                "type": "string",
                                "enum": [
                                  "critical",
                                  "warn",
                                  "info"
                                ]
                              },
                              "url": {
                                "type": "string",
                                "format": "uri"
                              },
                              "min_phi": {
                                "type": "number"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "The url is missing, blank, not https, or on a private/loopback/bare host.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "invalid_webhook"
                    },
                    "detail": {
                      "type": "string",
                      "const": "Provide an https:// URL on a public host."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "402": {
            "description": "The key's tier is `ground`. The check is literally `tier.name === 'ground'`, so solo/group/pro/enterprise all pass despite the copy saying \"group feature\".",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "upgrade"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "upgrade_required"
                    },
                    "detail": {
                      "type": "string",
                      "const": "Alerts are a group feature — the check and your own drift history stay free."
                    },
                    "upgrade": {
                      "type": "string",
                      "const": "https://phronesis.world/laserbrain"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      },
      "delete": {
        "summary": "Delete the drift-alert webhook config",
        "description": "Deletes KV `alert:<id>`. Not tier-gated and not conditional: returns the same body whether or not a config existed. Does not clear the alert log. src/api.ts:1346-1349.",
        "operationId": "deleteAlerts",
        "responses": {
          "200": {
            "description": "Deleted, or there was nothing to delete.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      }
    },
    "/v1/alerts/log": {
      "get": {
        "summary": "Read recent alert delivery attempts",
        "description": "Returns KV `alert-log:<id>`, or an empty array when absent. fireAlert() unshifts one entry per attempt and truncates to 50, so the array is newest-first and capped at 50; the key carries a 30-day TTL. Both suppressed attempts (muted mode, below min_phi, no valid webhook) and real HTTP attempts are logged. Not tier-gated. src/api.ts:1350-1352; writer at src/api.ts:471-474.",
        "operationId": "getAlertsLog",
        "responses": {
          "200": {
            "description": "Up to 50 attempts, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "log"
                  ],
                  "properties": {
                    "log": {
                      "type": "array",
                      "maxItems": 50,
                      "items": {
                        "type": "object",
                        "required": [
                          "ts",
                          "run_id",
                          "reason",
                          "severity",
                          "phi",
                          "delivered",
                          "status"
                        ],
                        "properties": {
                          "ts": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "run_id": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string",
                            "description": "The drift mode, e.g. stalled or goal-drift. Escalations are logged as `escalation:<reason>`, which is not in DEFAULT_SEVERITY and so grades as warn."
                          },
                          "severity": {
                            "type": "string",
                            "enum": [
                              "critical",
                              "warn",
                              "info"
                            ]
                          },
                          "phi": {
                            "type": "number"
                          },
                          "delivered": {
                            "type": "boolean",
                            "description": "True only when the webhook answered 2xx."
                          },
                          "status": {
                            "type": "integer",
                            "description": "HTTP status returned by the webhook; 0 when no request was made or the fetch threw."
                          },
                          "detail": {
                            "type": "string",
                            "description": "Optional. Written as `detail || undefined`, so it is absent on a successful delivery and present on failure or suppression (e.g. \"below min_phi\", \"<reason> muted\", \"webhook returned 500\", or a fetch error truncated to 200 chars)."
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      }
    },
    "/v1/alerts/test": {
      "post": {
        "summary": "Fire a synthetic drift alert at the configured webhook",
        "description": "Reads no request body. Calls fireAlert() with a fixed event — `run_id: \"test\"`, `goal: \"test alert\"`, `reason: \"stalled\"`, `phi: 0.62`, `advice: \"A test alert from laserbrain — your webhook is wired.\"` — and `force: true`, which bypasses the mute and min_phi checks but not the per-mode `url` override or the severity lookup (a `stalled` mode override still redirects the target and re-grades the severity; the default for stalled is `info`). The attempt is written to the alert log like any other. src/api.ts:1353-1359.",
        "operationId": "testAlert",
        "responses": {
          "200": {
            "description": "The attempt was made. `ok` is always true and does NOT mean the webhook accepted it — read `delivered`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "delivered",
                    "status",
                    "detail"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ],
                      "description": "Hardcoded true; reports that the handler ran, not that delivery succeeded."
                    },
                    "delivered": {
                      "type": "boolean",
                      "description": "True only when the webhook answered 2xx."
                    },
                    "status": {
                      "type": "integer",
                      "description": "HTTP status from the webhook; 0 if the fetch threw or the stored url failed re-validation."
                    },
                    "detail": {
                      "type": "string",
                      "description": "Empty string on success; otherwise \"no valid webhook configured\", \"webhook returned <status>\", or the fetch error truncated to 200 chars."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "No alert config is stored for this key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "no_config"
                    },
                    "detail": {
                      "type": "string",
                      "const": "Set a webhook first: POST /v1/alerts { url }"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "402": {
            "description": "The key's tier is `ground`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "upgrade"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "upgrade_required"
                    },
                    "detail": {
                      "type": "string",
                      "const": "Alerts are a group feature."
                    },
                    "upgrade": {
                      "type": "string",
                      "const": "https://phronesis.world/laserbrain"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      }
    },
    "/v1/check": {
      "post": {
        "summary": "Stateless drift check (no API key)",
        "description": "Replays a sequence of goal/progress/distance readings through the same checkStep detector POST /v1/drift uses, and returns a verdict per step. Nothing is persisted except the per-address rate-limit counter; the caller holds the ground and passes it in each call. Rate-limited to 100 calls/day per cf-connecting-ip (callers with no such header share one 'unknown' bucket). Because 'stalled' needs more than four distance readings and 'oscillating' needs a cycle, the two-field {ground, goal} form can only produce grounded, advancing, goal-drift or ungrammatical. parent_goal is not forwarded here, so 'excursion' is unreachable on this route. Handler: api.ts:991-1088.",
        "operationId": "postCheck",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Two accepted shapes. The sugar form requires both ground and goal; the sequence form requires steps. If steps is an array it is used and the sugar fields are ignored.",
                "properties": {
                  "ground": {
                    "type": "string",
                    "description": "Sugar form: the goal the run started with. Required and non-blank after trim when steps is absent, else 400."
                  },
                  "goal": {
                    "type": "string",
                    "description": "Sugar form: the goal just spelled. Required and non-blank after trim when steps is absent, else 400."
                  },
                  "progress": {
                    "type": "string",
                    "enum": [
                      "advancing",
                      "stuck",
                      "circling"
                    ],
                    "default": "advancing",
                    "description": "Sugar form. A non-string falls back to advancing; a string outside the enum is passed through and yields reason ungrammatical. It is applied ONLY to the second synthesized step: the ground step is hard-coded to advancing, so steps[0].progress in the response is always advancing no matter what was sent."
                  },
                  "distance": {
                    "description": "Sugar form, 0-10. Handled DIFFERENTLY from the sequence form: the handler first tests Number.isFinite(Number(distance)) and substitutes 5 when that fails, then the surviving value goes through asDist (parseInt(String(d),10), NaN becomes 5, clamped 0-10). So \"8abc\" becomes 5 here but 8 inside steps[]. The same value is applied to BOTH synthesized steps, so the distance term contributes nothing in this form.",
                    "oneOf": [
                      {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 10
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "steps": {
                    "type": "array",
                    "minItems": 2,
                    "maxItems": 64,
                    "description": "Sequence form. steps[0] becomes the ground. Fewer than 2 entries is a 400 bad_request; more than 64 is a 400 too_many_steps; an entry with a missing or blank goal is a 400 naming its index. A per-step parent_goal is NOT forwarded - the handler rebuilds each entry as {goal, progress, distance} only.",
                    "items": {
                      "type": "object",
                      "required": [
                        "goal"
                      ],
                      "properties": {
                        "goal": {
                          "type": "string",
                          "description": "Required, non-blank after trim. The trimmed value is what is measured and echoed."
                        },
                        "progress": {
                          "type": "string",
                          "enum": [
                            "advancing",
                            "stuck",
                            "circling"
                          ],
                          "default": "advancing",
                          "description": "A non-string falls back to advancing."
                        },
                        "distance": {
                          "description": "0-10, passed raw to asDist: parseInt(String(d),10), NaN becomes 5, clamped 0-10. Note there is no Number.isFinite pre-filter here, unlike the sugar form, so \"8abc\" yields 8.",
                          "oneOf": [
                            {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 10
                            },
                            {
                              "type": "string"
                            }
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verdicts for the replayed sequence.",
            "headers": {
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the daily counter resets."
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ground",
                    "steps",
                    "verdict",
                    "stateless",
                    "retained"
                  ],
                  "properties": {
                    "ground": {
                      "type": "string",
                      "description": "steps[0].goal, the goal everything was measured against."
                    },
                    "steps": {
                      "type": "array",
                      "description": "One entry per input step: the echoed input plus that step's verdict fields.",
                      "items": {
                        "type": "object",
                        "required": [
                          "goal",
                          "progress",
                          "distance",
                          "drifting",
                          "reason",
                          "phi",
                          "advice",
                          "laserscore",
                          "ground",
                          "goal_score",
                          "context"
                        ],
                        "properties": {
                          "goal": {
                            "type": "string",
                            "description": "The trimmed goal that was measured, not the raw input string."
                          },
                          "progress": {
                            "type": "string",
                            "description": "The progress that was measured. In the sugar form steps[0] is always advancing."
                          },
                          "distance": {
                            "type": "integer",
                            "minimum": 0,
                            "maximum": 10,
                            "description": "The coerced and clamped distance."
                          },
                          "drifting": {
                            "type": "boolean"
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "grounded",
                              "advancing",
                              "goal-drift",
                              "ungrammatical",
                              "stalled",
                              "oscillating",
                              "self-report:stuck",
                              "self-report:circling"
                            ],
                            "description": "drift.ts:551-613. excursion cannot occur on this route."
                          },
                          "phi": {
                            "type": "number",
                            "description": "Displacement from ground, rounded to 2dp; 0 for grounded and ungrammatical."
                          },
                          "advice": {
                            "type": "string"
                          },
                          "laserscore": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Canonical spelling, angle-bracketed sorted tokens plus progress and dN. Null exactly when the step is ungrammatical."
                          },
                          "ground": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The frozen ground goal at that step. NOT null on the first step: checkStep assigns st.ground before calling emit (drift.ts:553-557), and emit reads st.ground at drift.ts:532, so a grounded step returns the goal it just froze. It is null only on a step that returned ungrammatical while no ground had yet been set - the ungrammatical branch (drift.ts:551-552) returns before the ground is assigned."
                          },
                          "goal_score": {
                            "type": "number",
                            "description": "Jaccard overlap of this goal with the first goal; 1 when no first goal is set yet."
                          },
                          "context": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Stable context id, ctx_ plus base36 hash. Null when the goal is empty."
                          },
                          "repetition": {
                            "type": "integer",
                            "description": "Optional. Present only when this exact spelling has been written more than once within the replayed sequence."
                          },
                          "judgment": {
                            "type": "object",
                            "description": "Optional. Present only when judgeRun fires, which needs at least three steps, so never in the sugar form.",
                            "required": [
                              "verdict",
                              "because",
                              "counsel"
                            ],
                            "properties": {
                              "verdict": {
                                "type": "string",
                                "enum": [
                                  "abandon",
                                  "wrong-problem",
                                  "repeating",
                                  "narrow"
                                ]
                              },
                              "because": {
                                "type": "string"
                              },
                              "counsel": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    },
                    "verdict": {
                      "type": "object",
                      "description": "The last element of steps, same shape, including the echoed goal/progress/distance."
                    },
                    "stateless": {
                      "type": "boolean",
                      "const": true
                    },
                    "retained": {
                      "type": "string",
                      "const": "nothing"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "error is bad_request (body was not JSON, or was JSON that parsed to a falsy value - null, false, 0 or \"\" - since the handler tests !b; sugar form missing ground or goal; fewer than two steps; a step with no goal) or too_many_steps (more than 64). Every 400 body also carries the shapes and note usage hints, and every 400 carries the x-ratelimit-* headers because the rate-limit counter is bumped before the body is read.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "shapes",
                    "note"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "bad_request",
                        "too_many_steps"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "shapes": {
                      "type": "object",
                      "description": "Example request shapes.",
                      "properties": {
                        "two_step": {
                          "type": "object"
                        },
                        "sequence": {
                          "type": "object"
                        }
                      }
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until the daily counter resets."
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "Per-address daily cap reached (100/day). This body does NOT carry shapes/note.",
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "rate_limit"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/cost": {
      "get": {
        "summary": "Aggregate token cost across this account's retained drift runs",
        "description": "Walks the first 100 entries of `drift-index:<key>` — the index is unshifted on each fresh run (src/api.ts:1523-1526) and capped at 200, so those are genuinely the 100 most recent — and sums the per-step token costs reported to POST /v1/drift. Runs whose stored state has expired out of KV are skipped and do not count toward `runs`. Not metered. Handler: src/api.ts:1641-1661; runCost at src/api.ts:615-621.",
        "operationId": "getCost",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "Alternative credential accepted by bearer() (src/api.ts:940-946) in place of the Authorization header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Token totals across the scanned runs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tier",
                    "runs",
                    "runs_with_tokens",
                    "steps",
                    "tokens",
                    "overhead_tokens",
                    "work_tokens",
                    "overhead_fraction",
                    "tokens_per_step"
                  ],
                  "properties": {
                    "tier": {
                      "type": "string",
                      "enum": [
                        "ground",
                        "solo",
                        "group",
                        "pro",
                        "enterprise"
                      ]
                    },
                    "runs": {
                      "type": "integer",
                      "description": "Runs actually found in KV, out of the 100 most recent index entries."
                    },
                    "runs_with_tokens": {
                      "type": "integer",
                      "description": "Of those, how many had at least one step carrying a numeric token count."
                    },
                    "steps": {
                      "type": "integer",
                      "description": "Total trace steps across the scanned runs — every step, not only the instrumented ones."
                    },
                    "tokens": {
                      "type": "integer"
                    },
                    "overhead_tokens": {
                      "type": "integer",
                      "description": "Tokens on steps whose stored record has a truthy `overhead` flag."
                    },
                    "work_tokens": {
                      "type": "integer",
                      "description": "tokens - overhead_tokens."
                    },
                    "overhead_fraction": {
                      "type": "number",
                      "description": "overhead_tokens / tokens rounded to 4 decimals; 0 when tokens is 0."
                    },
                    "tokens_per_step": {
                      "type": "integer",
                      "description": "Math.round(tokens / steps); 0 when steps is 0."
                    },
                    "note": {
                      "type": "string",
                      "description": "Optional. Set only when tokens === 0 (the key is literally `undefined` otherwise and JSON.stringify drops it); asks the caller to report tokens on POST /v1/drift."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "No credential ('no_key', with detail) or an unknown one ('bad_key').",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present on no_key only."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "account"
        ]
      }
    },
    "/v1/data": {
      "post": {
        "summary": "Upload a corpus and score its drift from its own first window",
        "description": "Paid only: the gate is `tier.contactOnly === true || (tier.price ?? 0) >= 40`, so group ($40), pro ($400) and enterprise (contactOnly) pass and ground ($0) and solo ($20) do not. Body is capped at 5MB, checked first against the content-length header and then against the actual body length. Content-type decides the parser: anything containing 'json' is parsed as JSON, everything else as CSV (first line must be a header). Rows are analysed in the order sent. Not metered against the read/write counters. Handler: src/api.ts:1253-1326; analyse() at src/analytics.ts:138-258.\n\nCREDENTIAL: bearer() (src/api.ts:940-946) accepts `Authorization: Bearer <key>` or a `key` query parameter.",
        "operationId": "analyseData",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "Alternative credential accepted by bearer() (src/api.ts:940-946) in place of the Authorization header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "rows"
                    ],
                    "properties": {
                      "rows": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "data"
                    ],
                    "properties": {
                      "data": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                ],
                "description": "A bare array of row objects, or {rows: [...]}, or {data: [...]}. All three are accepted; valid JSON of any other shape is 400 bad_shape, and unparseable JSON is 400 unparseable."
              }
            },
            "text/csv": {
              "schema": {
                "type": "string",
                "description": "CSV whose first line is the header. Used for any content-type that does not contain 'json' — including a missing content-type, which reads as ''. parseCsv() drops all-blank rows and pads short rows with '' rather than throwing."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The corpus scored against the vocabulary of its first window.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "analysis",
                    "ordering"
                  ],
                  "properties": {
                    "analysis": {
                      "type": "object",
                      "required": [
                        "rows",
                        "columns",
                        "analysed",
                        "skipped",
                        "windows",
                        "windowSize",
                        "groundRows",
                        "trends",
                        "note"
                      ],
                      "properties": {
                        "rows": {
                          "type": "integer"
                        },
                        "columns": {
                          "type": "integer",
                          "description": "Count of keys on the FIRST row only — analyse() reads `Object.keys(rows[0])`, so a ragged JSON corpus whose later rows carry extra keys under-reports here."
                        },
                        "analysed": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Columns judged to carry language by textColumns(): at least max(3, 20% of the sampled 200 rows) non-empty values, not all pure numbers, and at least one value containing a space. Empty when nothing was scored."
                        },
                        "skipped": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Normally the columns textColumns() rejected — numeric, mostly-empty and id-like. BUT in the nothing-was-scored branch (fewer than 10 rows, or no column carried language) the handler returns `skipped: all`, i.e. EVERY column on the first row, including any that carry language."
                        },
                        "windows": {
                          "type": "integer",
                          "description": "max(2, min(8, floor(rows/5))) when scored; 0 when nothing was scored."
                        },
                        "windowSize": {
                          "type": "integer",
                          "description": "floor(rows / windows) when scored; 0 when nothing was scored."
                        },
                        "groundRows": {
                          "type": "integer",
                          "description": "Equal to windowSize; the frozen ground window."
                        },
                        "trends": {
                          "type": "array",
                          "description": "One entry per analysed column, sorted by `final` descending. Empty when fewer than 10 rows or no column carried language.",
                          "items": {
                            "type": "object",
                            "required": [
                              "column",
                              "series",
                              "final",
                              "crossedAt",
                              "direction",
                              "momentum",
                              "acceleration",
                              "forecast",
                              "rising",
                              "fading"
                            ],
                            "properties": {
                              "column": {
                                "type": "string"
                              },
                              "series": {
                                "type": "array",
                                "items": {
                                  "type": "number"
                                },
                                "description": "Jaccard token distance from the frozen ground window, one value per window AFTER the first (length windows-1, so 1 to 7 values), each rounded to 4 decimals."
                              },
                              "final": {
                                "type": "number",
                                "description": "Last value of series, or 0 when series is empty."
                              },
                              "crossedAt": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "1-based index INTO `series` where the distance first exceeded 0.5 (strictly >, not >=), else null. Because series starts at the second window, crossedAt = 1 means corpus window 2."
                              },
                              "direction": {
                                "type": "string",
                                "enum": [
                                  "drifting",
                                  "steady",
                                  "returning"
                                ],
                                "description": "final - series[0] > 0.1 is 'drifting', < -0.1 is 'returning', otherwise 'steady' — measured from the FIRST series point, not from the ground window itself."
                              },
                              "momentum": {
                                "type": "number",
                                "description": "Mean step delta over the second half of the series, 4 decimals; 0 when that half has fewer than 2 points."
                              },
                              "acceleration": {
                                "type": "number",
                                "description": "momentum minus the mean of the first half of the whole-series deltas, 4 decimals."
                              },
                              "forecast": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "description": "null unless the series has at least 5 points; least-squares fit extrapolated one window, values clamped to [0,1].",
                                "required": [
                                  "ahead",
                                  "value",
                                  "low",
                                  "high",
                                  "confidence"
                                ],
                                "properties": {
                                  "ahead": {
                                    "type": "integer",
                                    "description": "Always 1."
                                  },
                                  "value": {
                                    "type": "number"
                                  },
                                  "low": {
                                    "type": "number"
                                  },
                                  "high": {
                                    "type": "number"
                                  },
                                  "confidence": {
                                    "type": "string",
                                    "enum": [
                                      "low",
                                      "moderate"
                                    ],
                                    "description": "'moderate' only when the residual sd < 0.08 AND the series has at least 8 points; otherwise 'low'. Never 'high'."
                                  }
                                }
                              },
                              "rising": {
                                "type": "array",
                                "description": "At most 8 terms whose share of rows grew by >= 0.15 between the ground window and the last, sorted by delta descending.",
                                "items": {
                                  "type": "object",
                                  "required": [
                                    "term",
                                    "delta"
                                  ],
                                  "properties": {
                                    "term": {
                                      "type": "string"
                                    },
                                    "delta": {
                                      "type": "number",
                                      "description": "3 decimals."
                                    }
                                  }
                                }
                              },
                              "fading": {
                                "type": "array",
                                "description": "At most 8 terms whose share fell by >= 0.15, most-negative first.",
                                "items": {
                                  "type": "object",
                                  "required": [
                                    "term",
                                    "delta"
                                  ],
                                  "properties": {
                                    "term": {
                                      "type": "string"
                                    },
                                    "delta": {
                                      "type": "number",
                                      "description": "3 decimals, negative."
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "note": {
                          "type": "string",
                          "description": "Explains the frozen ground window, or why nothing was scored (under 10 rows / no language column)."
                        }
                      }
                    },
                    "ordering": {
                      "type": "string",
                      "description": "Constant reminder that rows were read in the order sent."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "'bad_shape' — parseable JSON that is not an array, {rows:[...]} or {data:[...]}. 'unparseable' — JSON.parse or parseCsv threw. 'empty' — zero rows after parsing (an empty CSV body reaches this; an empty JSON body throws and is 'unparseable' instead).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "bad_shape",
                        "unparseable",
                        "empty"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "No credential ('no_key', with detail) or an unknown one ('bad_key').",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present on no_key only."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "402": {
            "description": "The tier may not upload — price under $40 and not contact-only. Checked BEFORE the size cap and before the body is read, so an oversized upload from a ground key gets 402, not 413.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "your_tier",
                    "upgrade"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "upgrade_required"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "your_tier": {
                      "type": "string",
                      "enum": [
                        "ground",
                        "solo"
                      ],
                      "description": "tier.name — only ground and solo can reach this branch."
                    },
                    "upgrade": {
                      "type": "string",
                      "description": "Constant: https://phronesis.world/laserbrain/group"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "413": {
            "description": "Over the 5MB cap, by content-length or by actual body length. Both branches use error 'too_large' but different detail strings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "too_large"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "account"
        ]
      }
    },
    "/v1/dialogue": {
      "get": {
        "summary": "List dialogues, or read one conversation",
        "description": "No `id`: the account's dialogue index. With `id`: that conversation's fixed shared goal and turns. Not metered. api.ts:1734-1742.",
        "operationId": "getDialogue",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "A conv_id. Omit to list the index.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The index (no `id`) or one conversation (`id` given).",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "DialogueIndex",
                      "description": "When `id` is omitted.",
                      "type": "object",
                      "required": [
                        "tier",
                        "dialogues"
                      ],
                      "properties": {
                        "tier": {
                          "type": "string",
                          "enum": [
                            "ground",
                            "solo",
                            "group",
                            "pro",
                            "enterprise"
                          ]
                        },
                        "dialogues": {
                          "type": "array",
                          "description": "Newest first, capped at 200; [] when none. `goal` is the raw goal from the first turn and is absent if that turn sent none.",
                          "items": {
                            "type": "object",
                            "required": [
                              "conv_id",
                              "started"
                            ],
                            "properties": {
                              "conv_id": {
                                "type": "string"
                              },
                              "started": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "goal": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    },
                    {
                      "title": "DialogueDetail",
                      "description": "When `id` names a stored conversation.",
                      "type": "object",
                      "required": [
                        "conv_id",
                        "goal",
                        "turns"
                      ],
                      "properties": {
                        "conv_id": {
                          "type": "string"
                        },
                        "goal": {
                          "type": "string",
                          "description": "Normalised shared-goal tokens joined by spaces, not the raw string as sent."
                        },
                        "turns": {
                          "type": "array",
                          "description": "`pos` is that turn's position normalised to tokens.",
                          "items": {
                            "type": "object",
                            "required": [
                              "agent",
                              "pos",
                              "dist",
                              "reason",
                              "drifting"
                            ],
                            "properties": {
                              "agent": {
                                "type": "string"
                              },
                              "pos": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "dist": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 10
                              },
                              "reason": {
                                "type": "string",
                                "enum": [
                                  "grounded",
                                  "advancing",
                                  "ungrammatical",
                                  "topic-drift",
                                  "echo-spiral",
                                  "deliberation-stall"
                                ]
                              },
                              "drifting": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "404": {
            "description": "`id` given but no such conversation is stored for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "no_such_dialogue"
                    },
                    "detail": {
                      "type": "string",
                      "const": "unknown conversation, or it aged out of your tier retention"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      },
      "post": {
        "summary": "Submit one agent turn and get the collective drift verdict",
        "description": "The first turn fixes the shared goal (from `goal`, else `position`); later turns are measured against it. There is no 400 path - a malformed turn comes back as a verdict. Metered as a write; state retained for the tier's driftDays. api.ts:1698-1733, dialogue.ts:39-95.",
        "operationId": "postDialogue",
        "requestBody": {
          "required": false,
          "description": "An absent or non-JSON body is caught and treated as an empty object. No field is strictly required.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "conv_id": {
                    "type": "string",
                    "description": "String()-coerced. Omit to start a new conversation; the handler mints a UUID and returns it."
                  },
                  "agent": {
                    "type": "string",
                    "description": "String()-coerced, defaults to the literal agent. Also needed, together with `team`, to trigger recursion-team styling."
                  },
                  "position": {
                    "type": "string",
                    "description": "Effectively required after the first turn: an unnormalisable position yields reason ungrammatical with drifting true. On the first turn it is the fallback source of the shared goal."
                  },
                  "distance": {
                    "description": "parseInt(String(distance),10); NaN becomes 5, then clamped to 0-10. Number or numeric string.",
                    "oneOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "goal": {
                    "type": "string",
                    "description": "On the first turn it sets the fixed reference. On later turns, if too far from the shared goal, the verdict is topic-drift."
                  },
                  "team": {
                    "type": "string",
                    "description": "A preset (deep-search, iterative-refinement, adversarial-deliberation) or a team stored by this account. Styling applies only when `team` and `agent` are both present. Unlike /v1/modulate an unknown name does not 404 here: it resolves to no team, so every drift returns."
                  },
                  "role": {
                    "type": "string",
                    "description": "The role within `team` whose return policy applies; falls back to `agent`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The verdict for this turn. Always 200 - a drifting or ungrammatical turn is a verdict, not an error.",
            "headers": {
              "x-ratelimit-limit": {
                "description": "The tier's daily write limit.",
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "description": "Seconds until the daily counter resets.",
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "conv_id",
                    "drifting",
                    "reason",
                    "echo",
                    "dist",
                    "advice",
                    "turns"
                  ],
                  "properties": {
                    "conv_id": {
                      "type": "string",
                      "description": "The supplied conv_id, or the newly minted UUID."
                    },
                    "drifting": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "grounded",
                        "advancing",
                        "ungrammatical",
                        "topic-drift",
                        "echo-spiral",
                        "deliberation-stall"
                      ]
                    },
                    "echo": {
                      "type": "number",
                      "description": "Agreement with the other agents' last turns, 0-1 at 2dp; 0 on the first turn."
                    },
                    "dist": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 10,
                      "description": "The coerced distance for this turn."
                    },
                    "advice": {
                      "type": "string",
                      "description": "The detector's advice, REPLACED by the role's advice when team styling applied - the styled spread comes after the verdict spread."
                    },
                    "return": {
                      "type": "boolean",
                      "description": "Optional: present only when both `team` and `agent` were sent. Whether this role's policy says the agent should return."
                    },
                    "turns": {
                      "type": "integer",
                      "description": "Turn count in the conversation after this one."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Daily write quota exhausted.",
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "write_limit"
                    },
                    "detail": {
                      "type": "string",
                      "description": "Of the form: <tier> allows <n> turns/day."
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/digest": {
      "get": {
        "summary": "Read the scheduled digest config",
        "description": "Returns KV `digest:<id>` verbatim, or null when no digest is enabled. Not tier-gated. src/api.ts:1362-1364.",
        "operationId": "getDigest",
        "responses": {
          "200": {
            "description": "The stored digest config, or null.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "digest"
                  ],
                  "properties": {
                    "digest": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "required": [
                        "freq",
                        "created"
                      ],
                      "properties": {
                        "freq": {
                          "type": "string",
                          "enum": [
                            "daily",
                            "weekly"
                          ]
                        },
                        "created": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Optional. Absent when the digest reuses the alert webhook."
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      },
      "post": {
        "summary": "Enable or replace the scheduled digest",
        "description": "Writes KV `digest:<id>`, replacing any previous config wholesale. Both fields are optional and an absent or unparseable body is treated as `{}` — that succeeds and stores `{freq: \"daily\"}`. A cron walks every `digest:` key and sends the due ones (runDigests, src/api.ts:544-561): daily configs on every run, weekly configs only when the run lands on a UTC Monday, and only for accounts with at least one run in the window.\n\nWHAT LASERBRAIN POSTS TO YOUR WEBHOOK. digestBody (src/api.ts:510-522) uses `user-agent: laserbrain-digest/1`, `content-type: application/json`, a 5-second timeout, and the same host detection as alerts: Slack gets `{\"text\": string}`, Discord `{\"content\": string}`, and any other host gets `{\"event\": \"digest\", \"account\": string, \"period\": \"daily\"|\"weekly\", \"runs\": number, \"drifting\": number, \"events\": number, \"avgPhi\": number, \"byMode\": {\"<mode>\": number}, \"worst\": {\"goal\": string, \"phi\": number}|null, \"ts\": string}`. src/api.ts:1365-1374.",
        "operationId": "setDigest",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "freq": {
                    "type": "string",
                    "enum": [
                      "daily",
                      "weekly"
                    ],
                    "default": "daily",
                    "description": "Optional. Stored as `weekly` only on an exact string match; every other value, including a typo or an absent field, stores `daily`. Never rejected."
                  },
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Optional. Coerced with String() and trimmed. Omit or send an empty string to reuse the alert webhook at send time. A non-empty value must pass the same validWebhook check as alerts, or the request 400s."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Config stored. `digest` is the exact object written to KV.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "digest"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "digest": {
                      "type": "object",
                      "required": [
                        "freq",
                        "created"
                      ],
                      "properties": {
                        "freq": {
                          "type": "string",
                          "enum": [
                            "daily",
                            "weekly"
                          ]
                        },
                        "created": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri",
                          "description": "Optional. Spread in only when a non-empty valid url was supplied."
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "A non-empty url was supplied that is not https on a public host.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "invalid_webhook"
                    },
                    "detail": {
                      "type": "string",
                      "const": "Provide an https:// URL on a public host, or omit to reuse your alert webhook."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "402": {
            "description": "The key's tier is `ground`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "upgrade"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "upgrade_required"
                    },
                    "detail": {
                      "type": "string",
                      "const": "Scheduled digests are a group feature."
                    },
                    "upgrade": {
                      "type": "string",
                      "const": "https://phronesis.world/laserbrain"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      },
      "delete": {
        "summary": "Disable the scheduled digest",
        "description": "Deletes KV `digest:<id>`. Not tier-gated and unconditional: same body whether or not a config existed. src/api.ts:1375-1378.",
        "operationId": "deleteDigest",
        "responses": {
          "200": {
            "description": "Deleted, or there was nothing to delete.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      }
    },
    "/v1/digest/test": {
      "post": {
        "summary": "Send this account's digest immediately",
        "description": "Reads no request body. Calls sendDigest() with `force: true`, which skips the \"no runs in window\" check so an empty rollup is still delivered. The window is 7 days for a weekly config, 24 hours for daily, and the rollup reads at most 60 runs from `drift-index:<id>`. The target is the digest's own `url` if set, otherwise the url from the alert config. src/api.ts:1379-1385; sendDigest at src/api.ts:525-540; accountRollup at src/api.ts:488-507.",
        "operationId": "testDigest",
        "responses": {
          "200": {
            "description": "The send was attempted. Returned with status 200 even when nothing was sent — read `sent`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "sent",
                    "detail"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "Mirrors `sent` exactly (the handler returns `{ ok: r.sent, ...r }`)."
                    },
                    "sent": {
                      "type": "boolean",
                      "description": "True only when the webhook answered 2xx."
                    },
                    "detail": {
                      "type": "string",
                      "description": "\"delivered\" on success; otherwise \"no valid webhook\", \"webhook returned <status>\", or the fetch error truncated to 120 chars."
                    },
                    "rollup": {
                      "type": "object",
                      "description": "Optional. Absent only on the \"no valid webhook\" early return; present on every path that reached the fetch, including failures.",
                      "required": [
                        "runs",
                        "drifting",
                        "events",
                        "avgPhi",
                        "byMode",
                        "worst"
                      ],
                      "properties": {
                        "runs": {
                          "type": "integer",
                          "description": "Runs started inside the window, capped at 60."
                        },
                        "drifting": {
                          "type": "integer",
                          "description": "How many of those runs contained at least one drift step."
                        },
                        "events": {
                          "type": "integer",
                          "description": "Total drift steps across those runs."
                        },
                        "avgPhi": {
                          "type": "number",
                          "description": "Mean phi over every step in the window; 0 when there were no steps."
                        },
                        "byMode": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "integer"
                          },
                          "description": "Drift-step counts keyed by reason. Empty object when there was no drift."
                        },
                        "worst": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "description": "The run with the highest single-step phi, or null when the window held no readable runs.",
                          "required": [
                            "goal",
                            "phi"
                          ],
                          "properties": {
                            "goal": {
                              "type": "string",
                              "description": "The run's ground goal, falling back to the index goal, then the run_id."
                            },
                            "phi": {
                              "type": "number"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "No digest config is stored for this key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "no_config"
                    },
                    "detail": {
                      "type": "string",
                      "const": "Enable a digest first: POST /v1/digest { freq }"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "402": {
            "description": "The key's tier is `ground`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "upgrade"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "upgrade_required"
                    },
                    "detail": {
                      "type": "string",
                      "const": "Scheduled digests are a group feature."
                    },
                    "upgrade": {
                      "type": "string",
                      "const": "https://phronesis.world/laserbrain"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "operating"
        ]
      }
    },
    "/v1/drift": {
      "post": {
        "summary": "Log one drift step against a retained run",
        "description": "The stateful twin of POST /v1/check: the run's DriftState is kept in KV under the account key for the tier's retention window, so ground and history persist across calls. Metered as a write against the tier's daily allowance. On the transition into drift, and only for tiers other than ground, a configured alert webhook fires via waitUntil. Handler: api.ts:1489-1572.",
        "operationId": "postDrift",
        "requestBody": {
          "required": false,
          "description": "An absent or non-JSON body is caught and treated as {}, which yields reason ungrammatical rather than a 400.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "run_id": {
                    "type": "string",
                    "description": "Optional. Coerced with String(); absent or falsy mints a fresh crypto.randomUUID(). Reusing an id continues that run's stored state."
                  },
                  "goal": {
                    "type": "string",
                    "description": "The goal being spelled. Absent or blank yields reason ungrammatical. Also written verbatim (untruncated) to the run index on a run's first step, and to the account store truncated to 400 chars."
                  },
                  "progress": {
                    "type": "string",
                    "enum": [
                      "advancing",
                      "stuck",
                      "circling"
                    ],
                    "description": "Anything outside the enum yields reason ungrammatical."
                  },
                  "distance": {
                    "description": "0-10; coerced with parseInt(String(d)) and clamped, non-numeric becomes 5.",
                    "oneOf": [
                      {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 10
                      },
                      {
                        "type": "string"
                      }
                    ]
                  },
                  "parent_goal": {
                    "type": "string",
                    "description": "Optional. Not in the handler's declared body type, but the parsed body is passed straight to checkStep, which reads input.parent_goal: a declared parent whose overlap with the frozen first goal is at least 0.30 turns a would-be goal-drift into reason excursion, and it is appended to the laserscore."
                  },
                  "tokens": {
                    "type": "number",
                    "minimum": 0,
                    "description": "Optional per-step token cost. Recorded on the trace only when Number(tokens) is finite and >= 0; rounded and capped at 10000000. Also copied to the account store whenever Number(tokens) is finite - but that copy happens only for keys with an owner and only when the STORES binding is present."
                  },
                  "overhead": {
                    "type": "boolean",
                    "description": "Optional. Only the literal true marks this step as harness overhead rather than work."
                  },
                  "pair": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Optional A/B pair label. Honoured only on a run's first step; trimmed and truncated to 64 chars."
                  },
                  "arm": {
                    "type": "string",
                    "enum": [
                      "treatment",
                      "control"
                    ],
                    "description": "Optional. Honoured only on a run's first step; any other value is ignored."
                  },
                  "outcome": {
                    "type": "string",
                    "maxLength": 32,
                    "description": "Optional run outcome, settable on any step; trimmed and truncated to 32 chars."
                  },
                  "score": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "Optional run score, settable on any step; coerced with Number() and clamped to 0-1."
                  },
                  "agent": {
                    "type": "string",
                    "maxLength": 64,
                    "description": "Optional label recorded in the account store for account-bound keys only; defaults to unknown, truncated to 64 chars. Not part of the verdict."
                  },
                  "step": {
                    "type": "number",
                    "description": "Optional step number copied to the account store when Number(step) is finite, and only for keys with an owner. Not part of the verdict."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The verdict for this step, flattened onto the response body.",
            "headers": {
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "run_id",
                    "drifting",
                    "reason",
                    "phi",
                    "advice",
                    "laserscore",
                    "ground",
                    "goal_score",
                    "context",
                    "retained_for_s"
                  ],
                  "properties": {
                    "run_id": {
                      "type": "string",
                      "description": "The run this step was logged to, echoed or newly minted."
                    },
                    "drifting": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "grounded",
                        "advancing",
                        "goal-drift",
                        "excursion",
                        "ungrammatical",
                        "stalled",
                        "oscillating",
                        "self-report:stuck",
                        "self-report:circling"
                      ]
                    },
                    "phi": {
                      "type": "number",
                      "description": "Displacement from ground, rounded to 2dp."
                    },
                    "advice": {
                      "type": "string"
                    },
                    "laserscore": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Canonical spelling; null exactly when the step is ungrammatical. Carries the parent-containment suffix when parent_goal was sent."
                    },
                    "ground": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The frozen ground goal, returned on every step. NOT null on a run's first step: checkStep assigns st.ground before calling emit (drift.ts:553-557) and emit reads it at drift.ts:532, so the grounded step returns the goal it just froze. Null only while no ground has been set - i.e. on an ungrammatical step taken before any grounded step. Present at runtime via the extra spread in drift.ts:532 though not declared on the Verdict type."
                    },
                    "goal_score": {
                      "type": "number",
                      "description": "Jaccard overlap of this goal with the run's first goal; 1 before a first goal exists."
                    },
                    "context": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Stable context id, ctx_ plus base36 hash; null when the goal is empty."
                    },
                    "repetition": {
                      "type": "integer",
                      "description": "Optional. Present only when this exact spelling has been written more than once in this run."
                    },
                    "judgment": {
                      "type": "object",
                      "description": "Optional. Present only when judgeRun returns a hard verdict, which needs at least three recorded steps.",
                      "required": [
                        "verdict",
                        "because",
                        "counsel"
                      ],
                      "properties": {
                        "verdict": {
                          "type": "string",
                          "enum": [
                            "abandon",
                            "wrong-problem",
                            "repeating",
                            "narrow"
                          ]
                        },
                        "because": {
                          "type": "string"
                        },
                        "counsel": {
                          "type": "string"
                        }
                      }
                    },
                    "retained_for_s": {
                      "type": "integer",
                      "description": "tier.driftDays times 86400, the KV TTL this run was written with."
                    },
                    "notice": {
                      "type": "string",
                      "const": "no_contact_address",
                      "description": "Optional. Added at most once per UTC day, and only for keys with neither an email nor an owner."
                    },
                    "detail": {
                      "type": "string",
                      "description": "Optional. The prose accompanying notice; appears only alongside it."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "no_key when no Bearer header and no ?key= query param (carries detail); bad_key when the key does not resolve (no detail).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "The tier's daily write allowance is exhausted.",
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "write_limit"
                    },
                    "detail": {
                      "type": "string",
                      "description": "Names the tier and its steps/day allowance."
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/drift/outcome": {
      "post": {
        "summary": "Set a run's outcome and score without adding a step",
        "description": "Updates outcome and/or score on an existing retained run and rewrites it with a fresh tier-retention TTL. Adds no trace entry and is not metered. Handler: api.ts:1589-1599.",
        "operationId": "postDriftOutcome",
        "requestBody": {
          "required": false,
          "description": "Not actually required: an absent or non-JSON body is caught (.catch(() => ({}))) and treated as {}, which cannot resolve a run and therefore returns 404, never 400.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "run_id": {
                    "type": "string",
                    "description": "Required in practice. Coerced with String(); an absent or empty value cannot resolve a run, so the handler answers 404 rather than 400."
                  },
                  "outcome": {
                    "type": "string",
                    "maxLength": 32,
                    "description": "Optional. Applied only when it is a non-blank string; trimmed and truncated to 32 chars. Omitting it leaves any existing outcome unchanged, and there is no way to clear it here."
                  },
                  "score": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "Optional. Applied when non-null and Number(score) is finite; clamped to 0-1."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored values after the update.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "run_id",
                    "outcome",
                    "score"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "run_id": {
                      "type": "string",
                      "description": "The resolved run id."
                    },
                    "outcome": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "score": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "404": {
            "description": "Missing or empty run_id, or a run unknown to this key or aged out of retention.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "no_such_run"
                    },
                    "detail": {
                      "type": "string",
                      "const": "unknown run, or it aged out of your tier retention"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/drift/run": {
      "get": {
        "summary": "One run's full drift trace",
        "description": "Reads the stored DriftState for a run belonging to the authenticated key and returns its trace plus token cost. Not metered. Handler: api.ts:1581-1586.",
        "operationId": "getDriftRun",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "The run_id. Missing or unknown returns 404.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The run.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "run_id",
                    "goal",
                    "steps",
                    "cost",
                    "pair",
                    "arm",
                    "outcome",
                    "score",
                    "trace"
                  ],
                  "properties": {
                    "run_id": {
                      "type": "string",
                      "description": "Echo of the id query parameter, as the raw string from searchParams."
                    },
                    "goal": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The run's ground goal; null if no ground was ever set."
                    },
                    "steps": {
                      "type": "integer",
                      "description": "Number of recorded trace entries."
                    },
                    "cost": {
                      "type": "object",
                      "description": "runCost(), api.ts:615-621.",
                      "required": [
                        "steps",
                        "counted",
                        "tokens",
                        "overhead_tokens",
                        "work_tokens"
                      ],
                      "properties": {
                        "steps": {
                          "type": "integer"
                        },
                        "counted": {
                          "type": "integer",
                          "description": "How many steps carried a token count."
                        },
                        "tokens": {
                          "type": "integer",
                          "description": "Sum of reported tokens."
                        },
                        "overhead_tokens": {
                          "type": "integer",
                          "description": "Tokens on steps flagged overhead."
                        },
                        "work_tokens": {
                          "type": "integer",
                          "description": "tokens minus overhead_tokens."
                        }
                      }
                    },
                    "pair": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "arm": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "treatment, control, or null."
                    },
                    "outcome": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "score": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "trace": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "step",
                          "reason",
                          "phi"
                        ],
                        "properties": {
                          "step": {
                            "type": "integer",
                            "description": "1-based index within the run."
                          },
                          "reason": {
                            "type": "string",
                            "description": "The reading recorded for that step. Note oscillating is never stored here: the trace records the underlying reason and oscillating is derived at response time."
                          },
                          "phi": {
                            "type": "number"
                          },
                          "drifting": {
                            "type": "boolean",
                            "description": "Optional. Absent on records written before the field existed. It records the drifting flag the branch emitted, NOT the value returned to the caller: when the cycle detector overrides a step to oscillating the response says drifting true while the trace entry keeps the underlying branch's flag, which can be false."
                          },
                          "tokens": {
                            "type": "integer",
                            "description": "Optional. Present only when the step reported a token cost."
                          },
                          "overhead": {
                            "type": "boolean",
                            "description": "Optional. Present, and always true, only when the step was flagged overhead."
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "404": {
            "description": "No id given, or the run is unknown to this key or has aged out of retention.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "no_such_run"
                    },
                    "detail": {
                      "type": "string",
                      "const": "unknown run, or it aged out of your tier retention"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/drift/runs": {
      "get": {
        "summary": "List this account's retained drift runs",
        "description": "Returns the run index for the authenticated key, newest first, capped at the 200 most recently opened runs. Takes no parameters and is not metered. Handler: api.ts:1575-1578.",
        "operationId": "getDriftRuns",
        "responses": {
          "200": {
            "description": "The run index; runs is an empty array when nothing is retained.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tier",
                    "retention",
                    "runs"
                  ],
                  "properties": {
                    "tier": {
                      "type": "string",
                      "description": "The key's canonical tier name."
                    },
                    "retention": {
                      "type": "string",
                      "description": "Exactly \"24h (free — upgrade to retain)\" (em dash, not a hyphen) when driftDays <= 1; \"1 year\" when >= 365; otherwise the day count followed by d, e.g. \"30d\"."
                    },
                    "runs": {
                      "type": "array",
                      "description": "Index entries as written on each run's first step; not re-read from the run state, so they are not updated by later steps. Returned verbatim from KV and never re-validated, so entries written by older code may not carry every field below.",
                      "items": {
                        "type": "object",
                        "required": [
                          "run_id",
                          "started"
                        ],
                        "properties": {
                          "run_id": {
                            "type": "string"
                          },
                          "started": {
                            "type": "string",
                            "format": "date-time",
                            "description": "ISO timestamp of the run's first step."
                          },
                          "goal": {
                            "type": "string",
                            "description": "Optional. The raw, untruncated goal sent on the run's first step. The handler always writes the key, but JSON.stringify drops it when the value is undefined, so it is absent when that step carried no goal."
                          },
                          "pair": {
                            "type": "string",
                            "description": "Optional. Present only when the run was opened with a pair label."
                          },
                          "arm": {
                            "type": "string",
                            "enum": [
                              "treatment",
                              "control"
                            ],
                            "description": "Optional. Present only when the run was opened with an arm."
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/escalation": {
      "get": {
        "summary": "Poll one escalation, or list the queue",
        "description": "api.ts:1964-1976. Not metered and not tier-gated (unlike the POST). With ?id it returns the single record including any decision; without it, the newest 50 index entries are loaded and filtered by status.",
        "operationId": "getEscalation",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "An esc_id. When present, the response is the escalation record itself rather than the list envelope."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "pending"
            },
            "description": "Ignored when id is given. 'all' returns every loaded record; any other value is matched exactly against the record status, so only 'pending' and 'decided' can match."
          }
        ],
        "responses": {
          "200": {
            "description": "Either the single escalation record (when ?id was given) or the list envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "description": "Single record, returned when ?id matched.",
                      "required": [
                        "esc_id",
                        "run_id",
                        "reason",
                        "status",
                        "created"
                      ],
                      "properties": {
                        "esc_id": {
                          "type": "string"
                        },
                        "run_id": {
                          "type": "string",
                          "description": "Empty string when the escalation was posted without one."
                        },
                        "reason": {
                          "type": "string"
                        },
                        "advice": {
                          "type": "string",
                          "description": "Optional: only if supplied at POST."
                        },
                        "goal": {
                          "type": "string",
                          "description": "Optional: only if supplied at POST."
                        },
                        "streak": {
                          "type": "integer",
                          "description": "Optional: only if a finite streak was supplied at POST."
                        },
                        "detail": {
                          "type": "string",
                          "description": "Optional: only if supplied at POST."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending",
                            "decided"
                          ]
                        },
                        "created": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "decision": {
                          "type": "string",
                          "enum": [
                            "return",
                            "allow",
                            "stop"
                          ],
                          "description": "Optional: present once decided."
                        },
                        "decided": {
                          "type": "string",
                          "format": "date-time",
                          "description": "Optional: present once decided."
                        },
                        "note": {
                          "type": "string",
                          "description": "Optional: present when the decider supplied a note."
                        }
                      }
                    },
                    {
                      "type": "object",
                      "description": "List envelope, returned when ?id was absent.",
                      "required": [
                        "tier",
                        "pending",
                        "escalations"
                      ],
                      "properties": {
                        "tier": {
                          "type": "string"
                        },
                        "pending": {
                          "type": "integer",
                          "description": "Count of pending records among the loaded 50, regardless of the status filter."
                        },
                        "escalations": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "description": "Escalation records, same shape as the single-record response."
                          }
                        }
                      }
                    }
                  ]
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown Bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "404": {
            "description": "?id names no record for this key (unknown, or aged past tier retention).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_such_escalation"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      },
      "post": {
        "summary": "Escalate a persistent drift for human decision",
        "description": "api.ts:1936-1963. Refused on the free 'ground' tier. Metered as a WRITE. Every body field is optional — there is no 400 path. If an alert webhook is configured for the account, a page is fired in the background. Stored for tier.driftDays.",
        "operationId": "postEscalation",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "All fields optional; a missing or unparseable body is treated as {}.",
                "properties": {
                  "run_id": {
                    "type": "string",
                    "description": "Coerced with String(), truncated to 64 chars; stored as '' when absent."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Truncated to 48 chars; defaults to 'drift'."
                  },
                  "advice": {
                    "type": "string",
                    "description": "Truncated to 400 chars; omitted from the record when falsy."
                  },
                  "goal": {
                    "type": "string",
                    "description": "Truncated to 200 chars; omitted from the record when falsy."
                  },
                  "streak": {
                    "type": "integer",
                    "description": "Rounded and clamped to 0-9999; omitted from the record when not a finite number."
                  },
                  "detail": {
                    "type": "string",
                    "description": "Truncated to 400 chars; omitted from the record when falsy."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Escalation queued. Rate-limit headers included.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "esc_id",
                    "status",
                    "poll"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "esc_id": {
                      "type": "string",
                      "description": "'esc_' plus 18 hex characters."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending"
                      ]
                    },
                    "poll": {
                      "type": "string",
                      "description": "Literal '/v1/escalation?id=<esc_id>'."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown Bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "402": {
            "description": "The key is on the 'ground' tier; the hosted queue is a paid feature. Checked before metering.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "upgrade"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "upgrade_required"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "upgrade": {
                      "type": "string",
                      "description": "https://phronesis.world/laserbrain"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "Daily write budget exhausted; retry-after plus rate-limit headers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "write_limit"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/escalation/decide": {
      "post": {
        "summary": "Record a human decision on an escalation",
        "description": "api.ts:1977-1989. Not metered and not tier-gated. Sets status to 'decided' and re-stores the record for tier.driftDays. The record lookup happens before the decision is validated, so an unknown id returns 404 even when the decision is also invalid.",
        "operationId": "postEscalationDecide",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "decision"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "esc_id of a record belonging to this key. Missing or unknown is a 404."
                  },
                  "decision": {
                    "type": "string",
                    "enum": [
                      "return",
                      "allow",
                      "stop"
                    ],
                    "description": "Compared by strict equality; anything else is a 400."
                  },
                  "note": {
                    "type": "string",
                    "description": "Optional; truncated to 400 chars and stored on the record."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decision recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "esc_id",
                    "decision",
                    "status"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "esc_id": {
                      "type": "string"
                    },
                    "decision": {
                      "type": "string",
                      "enum": [
                        "return",
                        "allow",
                        "stop"
                      ]
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "decided"
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "decision was not one of return/allow/stop (checked after the record was found).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "bad_decision"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown Bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "404": {
            "description": "id absent, or no such record for this key (unknown or aged out of retention).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_such_escalation"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/experiment": {
      "get": {
        "summary": "Paired treatment/control token cost across retained runs",
        "description": "api.ts:1604-1633. Not metered and takes no parameters. Reads the newest 200 entries of this key's run index, keeps runs tagged with a pair and an arm of 'treatment' or 'control', and reports the token delta for pairs where both arms are present. Computed at the edge from stored runs; no upstream call.",
        "operationId": "getExperiment",
        "responses": {
          "200": {
            "description": "Pair summary. Empty array and zeros when nothing qualifies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tier",
                    "pairs_total",
                    "pairs_with_tokens",
                    "net_token_delta",
                    "mean_token_delta",
                    "treatment_cheaper_rate",
                    "same_outcome_pairs",
                    "reading",
                    "pairs"
                  ],
                  "properties": {
                    "tier": {
                      "type": "string"
                    },
                    "pairs_total": {
                      "type": "integer",
                      "description": "Pairs with both arms present."
                    },
                    "pairs_with_tokens": {
                      "type": "integer",
                      "description": "Of those, pairs where both arms reported non-zero tokens."
                    },
                    "net_token_delta": {
                      "type": "integer",
                      "description": "Sum of treatment minus control tokens over pairs_with_tokens; negative means treatment cheaper."
                    },
                    "mean_token_delta": {
                      "type": "integer",
                      "description": "net_token_delta / pairs_with_tokens, rounded; 0 when there are none."
                    },
                    "treatment_cheaper_rate": {
                      "type": "number",
                      "description": "Fraction of counted pairs with a negative delta, 2 decimal places; 0 when there are none."
                    },
                    "same_outcome_pairs": {
                      "type": "integer",
                      "description": "Pairs whose two arms recorded the same non-null outcome."
                    },
                    "reading": {
                      "type": "string",
                      "description": "Prose summary; a 'log matched runs' instruction when pairs_with_tokens is 0."
                    },
                    "pairs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "pair",
                          "treatment_tokens",
                          "control_tokens",
                          "token_delta",
                          "treatment_steps",
                          "control_steps",
                          "step_delta",
                          "treatment_outcome",
                          "control_outcome",
                          "same_outcome"
                        ],
                        "properties": {
                          "pair": {
                            "type": "string"
                          },
                          "treatment_tokens": {
                            "type": "integer"
                          },
                          "control_tokens": {
                            "type": "integer"
                          },
                          "token_delta": {
                            "type": "integer",
                            "description": "treatment_tokens minus control_tokens."
                          },
                          "treatment_steps": {
                            "type": "integer"
                          },
                          "control_steps": {
                            "type": "integer"
                          },
                          "step_delta": {
                            "type": "integer"
                          },
                          "treatment_outcome": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "control_outcome": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "same_outcome": {
                            "type": "boolean",
                            "description": "True only when both outcomes are non-null and equal."
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown Bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/guidance": {
      "post": {
        "summary": "Framework guidance from Alice",
        "description": "api.ts:1681-1692. Metered as a WRITE against the tier's write budget. Proxies the situation to https://phronesis.world/api/ai (NOT the laserbrain.fly.dev hub) with a 45s timeout and passes the reply through; the reply text is produced upstream, not at the edge. Rate-limit headers (x-ratelimit-limit/remaining/reset) are on the 200, 400 and 502 responses.",
        "operationId": "postGuidance",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "situation is used, falling back to question; both are coerced with String() and trimmed. At least one must be non-empty after trimming or the call is a 400. Truncated to 4000 chars before it is sent upstream.",
                "properties": {
                  "situation": {
                    "type": "string",
                    "description": "What the caller is facing."
                  },
                  "question": {
                    "type": "string",
                    "description": "Alias used only when situation is absent or empty."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Guidance from the upstream backend.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "reply",
                    "tier"
                  ],
                  "properties": {
                    "reply": {
                      "type": "string",
                      "description": "Upstream reply, trimmed; the literal '(no guidance returned)' when upstream returned an empty reply."
                    },
                    "tier": {
                      "type": "string",
                      "description": "Canonical tier name of the calling key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "Neither situation nor question was non-empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_situation"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown Bearer key (authenticate(), api.ts:192-211).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "Daily write budget exhausted. Sends retry-after plus the rate-limit headers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "write_limit"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "retry_after_s": {
                      "type": "integer",
                      "description": "Seconds to the UTC-day reset."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "502": {
            "description": "Upstream guidance backend returned a non-2xx status (alice_unreachable) or did not answer within 45s / threw (alice_timeout). The write has already been metered and is not refunded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "alice_unreachable",
                        "alice_timeout"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "For alice_unreachable: 'guidance backend returned <status>'."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/hear": {
      "post": {
        "summary": "Speak eight vocabulary words into the laserfield (field endpoint, not the detector)",
        "description": "NOT THE PRODUCT. One of the three 'field' endpoints — the laserfield weather system laserbrain grew out of, not the drift detector. The request body is PLAIN TEXT, not JSON: it is read with req.text(), trimmed, and split on whitespace. It must be exactly 8 words, and every word must be in the fixed 40-word VOCAB (src/api.ts:364-369), compared case-insensitively. Both checks run BEFORE metering, so a rejected utterance costs no quota. The accepted words are then forwarded verbatim (original casing, joined by single spaces) as the text body of POST {hub}/hear with a 30s timeout. Meters one write. The bearer key may also be supplied as ?key=<key>. Handler: src/api.ts:2100-2125.",
        "operationId": "postHear",
        "requestBody": {
          "required": true,
          "description": "Eight whitespace-separated words from the vocabulary. An empty body is rejected as eight_words with 'got 0'.",
          "content": {
            "text/plain": {
              "schema": {
                "type": "string",
                "description": "Exactly 8 whitespace-separated tokens. Each token, lowercased, must be one of the 40 VOCAB words: ground, body, bone, stone, soil, earth, dark, deep, cold, slow, breath, wind, flow, move, pass, reach, touch, come, go, walk, form, edge, surface, frame, line, curve, arc, space, skin, leaf, change, cross, shift, turn, break, fold, begin, door, fire, spark. Repeats are allowed; the handler only checks membership and count. The content-type header is never inspected, so any body type is read as text."
              },
              "example": "ground body stone breath wind flow edge door"
            }
          }
        },
        "responses": {
          "200": {
            "description": "The hub's reply, trimmed.",
            "headers": {
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "reply"
                  ],
                  "properties": {
                    "reply": {
                      "type": "string",
                      "description": "The hub's text response, trimmed. Not constrained to the vocabulary by this Worker."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Rejected before metering. Two distinct bodies; neither carries rate-limit headers. eight_words when the token count is not 8; vocabulary when one or more tokens are outside VOCAB (the count is checked first, so a body can only fail one of them per request).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "eight_words",
                        "vocabulary"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "For eight_words: 'got <n>'. For vocabulary: 'not in the vocabulary: <offending words, comma-separated>', echoing the words in their original casing."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not in the store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "410": {
            "description": "Same field_retired body as /v1/signal, returned when LASERBRAIN_FIELD_RETIRED is not the string 'false'. Inactive in the deployed config. Checked before the body is read, so it precedes the 400 validation. Not metered. src/api.ts:2043-2055.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retired",
                    "still_free"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "field_retired"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "retired": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "still_free": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "Daily write quota exhausted. Hard stop, no overage.",
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "upgrade",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "write_limit"
                    },
                    "detail": {
                      "type": "string",
                      "description": "'<tier> allows <n> writes/day. Hard stop — you will not be billed for overage.'"
                    },
                    "upgrade": {
                      "type": "string",
                      "const": "/v1/tiers"
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "The hub was unreachable, non-2xx, or its body would not read. The metered write is refunded best-effort.",
            "headers": {
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "refunded"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "field_unreachable"
                    },
                    "refunded": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "field"
        ]
      }
    },
    "/v1/history": {
      "get": {
        "summary": "Laserfield signal series (field endpoint, not the detector)",
        "description": "NOT THE PRODUCT. One of the three 'field' endpoints — the laserfield weather system laserbrain grew out of, not the drift detector. Proxies GET {hub}/history?limit=&since= as JSON with a 25s timeout and returns the hub's body spread into the response with window_hours added. The tier's history window is enforced here, not by the hub: since is raised to now-historyHours if the tier caps it. Meters one read. The bearer key may also be supplied as ?key=<key>. Handler: src/api.ts:2075-2097.",
        "operationId": "getHistory",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Coerced with Number() then Math.min(x, 5000). Absent, non-numeric or 0 falls back to 500. Negative values are not clamped and are forwarded to the hub as given.",
            "schema": {
              "type": "integer",
              "default": 500,
              "maximum": 5000
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Unix seconds. Coerced with Number(); absent or non-numeric becomes 0. Then raised to the tier floor (now - historyHours*3600) when the tier has a non-zero historyHours, so a caller cannot read further back than their tier allows.",
            "schema": {
              "type": "number",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The hub's /history document spread at the top level, plus window_hours.",
            "headers": {
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "required": [
                    "window_hours"
                  ],
                  "properties": {
                    "window_hours": {
                      "description": "The tier's history window in hours, or the string 'unlimited' when the tier's historyHours is 0.",
                      "oneOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "string",
                          "const": "unlimited"
                        }
                      ]
                    }
                  },
                  "description": "All other keys come from the hub and are not shaped by this Worker. A hub key named window_hours would be overwritten."
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not in the store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "410": {
            "description": "Same field_retired body as /v1/signal, returned when LASERBRAIN_FIELD_RETIRED is not the string 'false'. Inactive in the deployed config. Not metered. src/api.ts:2043-2055.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retired",
                    "still_free"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "field_retired"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "retired": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "still_free": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "Daily read quota exhausted. Note this body carries NO detail key, unlike the otherwise identical 429 on /v1/signal.",
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "read_limit"
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "The hub was unreachable, non-2xx, or its body would not read. The metered read is refunded best-effort.",
            "headers": {
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "refunded"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "field_unreachable"
                    },
                    "refunded": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "field"
        ]
      }
    },
    "/v1/keys": {
      "post": {
        "summary": "Disabled — keys are issued through a GitHub account",
        "description": "Public, and always refuses. The handler (src/api.ts:1111-1121) sits above the `const auth = await authenticate(req, env)` line at src/api.ts:1135 and returns 403 account_required unconditionally; it reads no request body and no credential. Anonymous key minting closed 2026-08-17. The route is kept rather than deleted so an old client gets a 403 with somewhere to go instead of a 404.",
        "operationId": "createKey",
        "security": [],
        "responses": {
          "403": {
            "description": "Always. No key is ever created by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "sign_in",
                    "note"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "account_required"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "sign_in": {
                      "type": "string",
                      "description": "Constant: https://phronesis.world/laserbrain"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "account"
        ]
      }
    },
    "/v1/me": {
      "get": {
        "summary": "This key's tier and today's usage",
        "description": "Reads today's read/write counters for the calling key with peekUsage(), which does not increment them — this route is not metered. Handler: src/api.ts:1141-1166, below the authenticate() call at src/api.ts:1135.\n\nCREDENTIAL: bearer() (src/api.ts:940-946) accepts EITHER `Authorization: Bearer <key>` OR a `key` query parameter. Both are live on every authenticated route here; see the `key` parameter below.",
        "operationId": "getMe",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "Undocumented-until-now alternative credential. bearer() at src/api.ts:940-946 falls back to `new URL(req.url).searchParams.get('key')` when there is no Authorization header, so the raw key may be passed in the URL. Supply this OR the Authorization header; omitting both is 401 no_key.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tier, limits, and usage so far in the current UTC day.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tier",
                    "price",
                    "day",
                    "machines",
                    "reads",
                    "writes",
                    "resets_in_s",
                    "contact"
                  ],
                  "properties": {
                    "tier": {
                      "type": "string",
                      "enum": [
                        "ground",
                        "solo",
                        "group",
                        "pro",
                        "enterprise"
                      ]
                    },
                    "price": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "USD per month; null on enterprise."
                    },
                    "day": {
                      "type": "string",
                      "description": "UTC day stamp, YYYY-MM-DD (dayStamp() in src/tiers.ts)."
                    },
                    "machines": {
                      "type": "integer"
                    },
                    "reads": {
                      "type": "object",
                      "required": [
                        "used",
                        "limit"
                      ],
                      "properties": {
                        "used": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        }
                      }
                    },
                    "writes": {
                      "type": "object",
                      "required": [
                        "used",
                        "limit"
                      ],
                      "properties": {
                        "used": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        }
                      }
                    },
                    "resets_in_s": {
                      "type": "integer",
                      "description": "Seconds until the next UTC midnight; secondsToReset() floors this at 1, so it is never 0.",
                      "minimum": 1
                    },
                    "contact": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The key's email if set; otherwise 'via account <owner>' if the key has an owner; otherwise null."
                    },
                    "notice": {
                      "type": "string",
                      "description": "Optional. Spread in only when the key has neither an email nor an owner — the same case in which contact is null. Text asks the holder to POST /v1/me/email. Note this is a plain sentence, unlike the separate once-a-day reachNotice() object (src/api.ts:1183-1196) attached to other routes, whose `notice` value is the token 'no_contact_address'."
                    },
                    "key_exposure": {
                      "type": "object",
                      "description": "Present ONLY when this key has been sent in a query string. Absent otherwise — an exposed:false on every response trains a reader to skip the one field that must not be skipped the day it appears. Recorded once per UTC day per key.",
                      "required": [
                        "exposed",
                        "detail",
                        "days_seen",
                        "first_at",
                        "last_at",
                        "paths"
                      ],
                      "properties": {
                        "exposed": {
                          "type": "boolean",
                          "const": true
                        },
                        "detail": {
                          "type": "string"
                        },
                        "days_seen": {
                          "type": "integer",
                          "description": "Distinct UTC days on which the key arrived in a URL, not a request count."
                        },
                        "first_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "last_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "paths": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "maxItems": 20
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "No credential (error 'no_key', with a detail string) or an unknown one (error 'bad_key', no detail). Produced by authenticate() at src/api.ts:192-208.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present on no_key only."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "account"
        ]
      }
    },
    "/v1/me/email": {
      "post": {
        "summary": "Attach or remove a contact address on this key",
        "description": "Stores the address on the key record unverified. The value is trimmed, truncated to 254 characters and lowercased before validation. Sending an empty string — or any whitespace-only string, since the trim runs first — deletes the address. Handler: src/api.ts:1213-1239.\n\nWHAT IS WRITTEN TO THE KeyRecord: on attach, `{...rec, email, emailAt: <ISO now>, emailVerified: false}`. On removal, `{...rec, email: undefined, emailAt: undefined, emailVerified: undefined}` — emailVerified is set to undefined (i.e. dropped), NOT to false. Nothing here ever sets emailVerified true; only something that actually sends mail may.\n\nCREDENTIAL: as on every authenticated route, bearer() accepts `Authorization: Bearer <key>` or a `key` query parameter (src/api.ts:940-946). The handler then re-derives the raw key with `bearer(req)!` and re-hashes it to address the KV record.",
        "operationId": "setMyEmail",
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "Alternative credential accepted by bearer() (src/api.ts:940-946) in place of the Authorization header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Must be a JSON string or the handler 400s bad_request (the check is `!b || typeof b.email !== 'string'`, so a null/non-object body, a missing field, or a non-string all land here). An empty or whitespace-only string removes the stored address. A non-empty value must match /^[^@\\s]+@[^@\\s.]+\\.[^@\\s]+$/ after trim/truncate-to-254/lowercase."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Address attached, or removed when an empty (or whitespace-only) string was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "email",
                    "verified",
                    "detail"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "email": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The normalised stored address, or null after a removal."
                    },
                    "verified": {
                      "type": "boolean",
                      "enum": [
                        false
                      ],
                      "description": "Hard-coded false in the response literal — this endpoint never verifies an address."
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "'bad_request' when the body is not JSON, is JSON null, or `email` is not a string; 'bad_email' when a non-empty value fails the address pattern.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "bad_request",
                        "bad_email"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "No credential ('no_key', with detail) or an unknown one ('bad_key').",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present on no_key only."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "account"
        ]
      }
    },
    "/v1/modulate": {
      "post": {
        "summary": "Single-agent drift check plus role-styled return policy",
        "description": "api.ts:1764-1816. Runs the same checkStep detector as /v1/drift (drift.ts:442-614) over a stored run, then applies a recursion-team role policy (teams.ts styleReturn) to decide whether this role should return. Computed at the edge; no upstream call. Metered as a WRITE. Run state is persisted under the calling key for tier.driftDays.",
        "operationId": "postModulate",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "goal"
                ],
                "properties": {
                  "run_id": {
                    "type": "string",
                    "description": "Optional. A new UUID is minted when absent, so omitting it starts a fresh run."
                  },
                  "goal": {
                    "type": "string",
                    "description": "Required: 400 when absent or blank after trimming. The first goal seen for a run_id freezes the ground."
                  },
                  "progress": {
                    "type": "string",
                    "enum": [
                      "advancing",
                      "stuck",
                      "circling"
                    ],
                    "description": "Anything outside this set (including absent) yields the 'ungrammatical' verdict at 200, not an error status."
                  },
                  "distance": {
                    "type": [
                      "integer",
                      "string",
                      "number",
                      "null"
                    ],
                    "description": "Parsed with parseInt and clamped to 0-10; unparseable or absent becomes 5."
                  },
                  "team": {
                    "type": "string",
                    "description": "Preset name or a team template stored on this account. Unknown names are a 404, never a silent fallback."
                  },
                  "role": {
                    "type": "string",
                    "description": "Role within the team. Unknown role with a known team is a 404."
                  },
                  "parent_goal": {
                    "type": "string",
                    "description": "Not named in the handler's type cast, but the parsed body object is passed straight to checkStep, which reads input.parent_goal to allow the 'excursion' verdict."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verdict fields are spread at the top level from checkStep's Verdict (drift.ts:155-178).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "run_id",
                    "drifting",
                    "reason",
                    "phi",
                    "advice",
                    "laserscore",
                    "goal_score",
                    "context",
                    "modulation"
                  ],
                  "properties": {
                    "run_id": {
                      "type": "string"
                    },
                    "drifting": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "grounded",
                        "advancing",
                        "ungrammatical",
                        "goal-drift",
                        "excursion",
                        "stalled",
                        "oscillating",
                        "self-report:stuck",
                        "self-report:circling"
                      ]
                    },
                    "phi": {
                      "type": "number",
                      "description": "Displacement from ground, 2 decimal places."
                    },
                    "advice": {
                      "type": "string"
                    },
                    "laserscore": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Canonical spelling of this step; null when the step cannot be spelled."
                    },
                    "goal_score": {
                      "type": "number",
                      "description": "Jaccard overlap of this goal with the run's frozen first goal; 1 when no ground is set yet."
                    },
                    "context": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Stable ctx_ id for the goal; null for an empty goal."
                    },
                    "ground": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The run's frozen ground goal, spread in on every verdict; null before a ground is set."
                    },
                    "repetition": {
                      "type": "integer",
                      "description": "Optional: spread in only when this exact spelling has occurred more than once in the run."
                    },
                    "judgment": {
                      "type": "object",
                      "description": "Optional: present only when judgeRun returns one (drift.ts:259).",
                      "properties": {
                        "verdict": {
                          "type": "string"
                        },
                        "because": {
                          "type": "string"
                        },
                        "counsel": {
                          "type": "string"
                        }
                      }
                    },
                    "modulation": {
                      "type": "object",
                      "required": [
                        "return",
                        "advice",
                        "team",
                        "role",
                        "basis"
                      ],
                      "properties": {
                        "return": {
                          "type": "boolean",
                          "description": "Whether this role should return to ground for this verdict."
                        },
                        "advice": {
                          "type": "string",
                          "description": "Role-styled advice; the detector's advice when unstyled."
                        },
                        "team": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "role": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "basis": {
                          "type": "string",
                          "description": "Why the policy decided as it did: 'unstyled — every drift returns', '<role> recurses <policy>', or 'team given without a role — unstyled'."
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "goal missing or blank.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_goal"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown Bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "404": {
            "description": "team names no preset or stored template (no_such_team), or team is known and role is not (no_such_role). The drift state has already been written and the write already metered.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_such_team",
                        "no_such_role"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Lists the available presets or the team's roles."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "Daily write budget exhausted; retry-after plus rate-limit headers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "write_limit"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/openapi.json": {
      "get": {
        "summary": "This document",
        "description": "The machine-readable description of this API. Public — no key. Cached for 300s. A parity gate fails the build when this document and the server's own route table disagree about which routes exist.",
        "operationId": "getOpenapi",
        "tags": [
          "account"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "This OpenAPI 3.1 document.",
            "headers": {
              "cache-control": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/operator/consult": {
      "post": {
        "summary": "Ask whether an irreversible act should proceed",
        "description": "api.ts:1839-1901. Metered as a READ. Advisory only — it enforces nothing. Checks this agent's own last drift reading and whether a peer in the same group already claimed the same act; on a clear answer it claims the act for 900s. Computed at the edge from KV, with no upstream call. Every outcome except the rate limit is HTTP 200 with allow true or false.",
        "operationId": "postOperatorConsult",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind",
                  "target"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "description": "What is about to be done. Coerced with String() and trimmed; empty is a 400."
                  },
                  "target": {
                    "type": "string",
                    "description": "What it is done to. Coerced with String() and trimmed; empty is a 400."
                  },
                  "reversible": {
                    "type": "boolean",
                    "description": "Only the strict value true counts as reversible; anything else (including absent) is treated as guarded."
                  },
                  "outward": {
                    "type": "boolean",
                    "description": "Strict true forces the guarded path even when reversible is true."
                  },
                  "run_id": {
                    "type": "string",
                    "description": "Optional. Without it the drift half of the check is skipped and checked.drift stays false."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Advisory verdict. Rate-limit headers included.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "allow",
                    "reason",
                    "detail",
                    "checked"
                  ],
                  "properties": {
                    "allow": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "reversible",
                        "drifting",
                        "duplicate",
                        "clear"
                      ],
                      "description": "'reversible' (not guarded, nothing checked), 'drifting' (this run's last step was drifting), 'duplicate' (a peer holds the act), 'clear' (act claimed for 900s)."
                    },
                    "detail": {
                      "type": "string"
                    },
                    "checked": {
                      "type": "object",
                      "required": [
                        "drift",
                        "group"
                      ],
                      "description": "Which checks actually ran, so an allow that skipped both is distinguishable from one that passed both.",
                      "properties": {
                        "drift": {
                          "type": "boolean",
                          "description": "True only when run_id was given and a stored run was found."
                        },
                        "group": {
                          "type": "boolean",
                          "description": "True once the group lookup ran."
                        }
                      }
                    },
                    "holder": {
                      "type": "object",
                      "description": "Optional: present only on reason 'duplicate'.",
                      "required": [
                        "member",
                        "at"
                      ],
                      "properties": {
                        "member": {
                          "type": "string",
                          "description": "Key id of the peer holding the act."
                        },
                        "at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "run_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "kind or target missing/blank.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_act"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown Bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "Daily read budget exhausted. Body carries no detail field, unlike the write-limit responses.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "read_limit"
                      ]
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/recursion-team": {
      "get": {
        "summary": "List recursion-team templates, or fetch one by name",
        "description": "No `name`: the built-in presets plus this account's custom teams. With `name`: resolves a preset first, then a team stored by this account. Not metered. api.ts:1906-1913.",
        "operationId": "getRecursionTeam",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "A preset name (deep-search, iterative-refinement, adversarial-deliberation) or a custom team name. Omit to list everything.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The full listing (no `name`) or one team (`name` given). In every team object `builtin` is true only on presets, `created` is absent on presets, and per role `modes` (explicit drift modes overriding the depth default) and `return` (the advice used when the policy fires) are optional.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "title": "TeamListing",
                      "description": "When `name` is omitted.",
                      "type": "object",
                      "required": [
                        "presets",
                        "custom"
                      ],
                      "properties": {
                        "presets": {
                          "type": "array",
                          "description": "The three built-in teams.",
                          "items": {
                            "type": "object",
                            "required": [
                              "name",
                              "task",
                              "roles"
                            ],
                            "properties": {
                              "name": {
                                "type": "string"
                              },
                              "task": {
                                "type": "string"
                              },
                              "builtin": {
                                "type": "boolean"
                              },
                              "created": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "roles": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "required": [
                                    "role",
                                    "recurse"
                                  ],
                                  "properties": {
                                    "role": {
                                      "type": "string"
                                    },
                                    "recurse": {
                                      "type": "string",
                                      "enum": [
                                        "deep",
                                        "balanced",
                                        "tight"
                                      ]
                                    },
                                    "modes": {
                                      "type": "array",
                                      "items": {
                                        "type": "string"
                                      }
                                    },
                                    "return": {
                                      "type": "string"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "custom": {
                          "type": "array",
                          "description": "This account's stored teams, newest first, capped at 50; [] when none.",
                          "items": {
                            "type": "object",
                            "required": [
                              "name",
                              "task",
                              "roles",
                              "created"
                            ],
                            "properties": {
                              "name": {
                                "type": "string"
                              },
                              "task": {
                                "type": "string"
                              },
                              "created": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "roles": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "required": [
                                    "role",
                                    "recurse"
                                  ],
                                  "properties": {
                                    "role": {
                                      "type": "string"
                                    },
                                    "recurse": {
                                      "type": "string",
                                      "enum": [
                                        "deep",
                                        "balanced",
                                        "tight"
                                      ]
                                    },
                                    "modes": {
                                      "type": "array",
                                      "items": {
                                        "type": "string"
                                      }
                                    },
                                    "return": {
                                      "type": "string"
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    },
                    {
                      "title": "TeamFetch",
                      "description": "When `name` resolves to a preset or a stored team.",
                      "type": "object",
                      "required": [
                        "team"
                      ],
                      "properties": {
                        "team": {
                          "type": "object",
                          "required": [
                            "name",
                            "task",
                            "roles"
                          ],
                          "properties": {
                            "name": {
                              "type": "string"
                            },
                            "task": {
                              "type": "string"
                            },
                            "builtin": {
                              "type": "boolean"
                            },
                            "created": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "roles": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "required": [
                                  "role",
                                  "recurse"
                                ],
                                "properties": {
                                  "role": {
                                    "type": "string"
                                  },
                                  "recurse": {
                                    "type": "string",
                                    "enum": [
                                      "deep",
                                      "balanced",
                                      "tight"
                                    ]
                                  },
                                  "modes": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "return": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "404": {
            "description": "`name` matched neither a preset nor a team stored by this account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "no_such_team"
                    },
                    "detail": {
                      "type": "string",
                      "description": "Of the form: unknown team; presets: deep-search, iterative-refinement, adversarial-deliberation."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      },
      "post": {
        "summary": "Store a custom recursion-team template",
        "description": "Validates and normalises the body, stores it under this account, and unshifts it into the account's team index (capped at 50, replacing any same-named entry). Upserts - re-posting a name overwrites. Not metered. api.ts:1914-1922, teams.ts:83-99.",
        "operationId": "postRecursionTeam",
        "requestBody": {
          "required": true,
          "description": "A non-JSON body is treated as an empty object, which 400s. Unknown top-level fields are dropped by sanitizeTeam.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "roles"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Lowercased, stripped to a-z 0-9 and hyphen, truncated to 40 chars, and must be non-empty after that. May not equal a preset name."
                  },
                  "task": {
                    "type": "string",
                    "description": "Optional: String()-coerced, truncated to 200 chars, defaults to an empty string."
                  },
                  "roles": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 8,
                    "description": "Non-empty; only the first 8 are kept.",
                    "items": {
                      "type": "object",
                      "required": [
                        "role"
                      ],
                      "properties": {
                        "role": {
                          "type": "string",
                          "description": "Trimmed, truncated to 40 chars, must be non-empty."
                        },
                        "recurse": {
                          "type": "string",
                          "enum": [
                            "deep",
                            "balanced",
                            "tight"
                          ],
                          "default": "balanced",
                          "description": "Optional: any other value silently becomes balanced."
                        },
                        "modes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Optional: filtered to known drift modes and truncated to 8, dropped entirely if empty after filtering. Overrides the depth default."
                        },
                        "return": {
                          "type": "string",
                          "description": "Optional: kept only if a string, truncated to 240 chars."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored. `team` is the sanitised form and may differ from what was sent; per role `modes` is present only when non-empty after filtering and `return` only when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "team"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "team": {
                      "type": "object",
                      "required": [
                        "name",
                        "task",
                        "roles",
                        "created"
                      ],
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "task": {
                          "type": "string"
                        },
                        "created": {
                          "type": "string",
                          "format": "date-time",
                          "description": "Set by the server at store time."
                        },
                        "roles": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "role",
                              "recurse"
                            ],
                            "properties": {
                              "role": {
                                "type": "string"
                              },
                              "recurse": {
                                "type": "string",
                                "enum": [
                                  "deep",
                                  "balanced",
                                  "tight"
                                ]
                              },
                              "modes": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "return": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "Validation failed, or the name collides with a built-in preset.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "invalid_team",
                        "reserved_name"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "For invalid_team one of: expected a JSON object / name required (a-z, 0-9, - ; max 40) / roles required (a non-empty array) / each role needs a role name. For reserved_name: that name is a built-in preset - pick another."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      },
      "delete": {
        "summary": "Delete a custom recursion-team template",
        "description": "Deletes the stored team and filters it out of the account's index. Idempotent and unvalidated - a missing or unknown `name` still returns 200 ok, and presets are unaffected since they live in code, not storage. Not metered. api.ts:1923-1929.",
        "operationId": "deleteRecursionTeam",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "The custom team name. Defaults to the empty string when omitted, which deletes nothing but still returns 200.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted, or nothing matched.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/self": {
      "get": {
        "summary": "Read the persisted self and its session log",
        "description": "Returns the stored self, its log, and - only when `identity` is supplied AND a self exists - a continuity reading against the ground identity. Not metered. api.ts:1666-1669, 653-660.",
        "operationId": "getSelf",
        "parameters": [
          {
            "name": "identity",
            "in": "query",
            "required": false,
            "description": "A current identity statement to measure against the stored ground identity.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The stored self. Returns 200 with self null when nothing is stored.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tier",
                    "retained_days",
                    "self",
                    "log",
                    "continuity"
                  ],
                  "properties": {
                    "tier": {
                      "type": "string",
                      "enum": [
                        "ground",
                        "solo",
                        "group",
                        "pro",
                        "enterprise"
                      ]
                    },
                    "retained_days": {
                      "type": "integer",
                      "description": "The tier's driftDays."
                    },
                    "self": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "null when this account has never posted a self. ground.purpose is an empty string if the first POST sent none; `mind` is omitted when never set or set to an empty string.",
                      "required": [
                        "ground",
                        "now",
                        "updated",
                        "sessions"
                      ],
                      "properties": {
                        "ground": {
                          "type": "object",
                          "required": [
                            "identity",
                            "purpose",
                            "set"
                          ],
                          "properties": {
                            "identity": {
                              "type": "string"
                            },
                            "purpose": {
                              "type": "string"
                            },
                            "set": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        },
                        "now": {
                          "type": "string"
                        },
                        "mind": {
                          "type": "string"
                        },
                        "updated": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "sessions": {
                          "type": "integer"
                        }
                      }
                    },
                    "log": {
                      "type": "array",
                      "description": "Newest first, capped at 100; [] when none. `note` is that POST's note else its now, truncated to 280 chars. `phi` is present only when that POST supplied an identity to compare.",
                      "items": {
                        "type": "object",
                        "required": [
                          "ts",
                          "note",
                          "sessions"
                        ],
                        "properties": {
                          "ts": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "note": {
                            "type": "string"
                          },
                          "sessions": {
                            "type": "integer"
                          },
                          "phi": {
                            "type": "number"
                          }
                        }
                      }
                    },
                    "continuity": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "null unless BOTH a stored self exists and `identity` was supplied. phi is 1 minus the Jaccard overlap of identity words, 0-1 at 2dp.",
                      "required": [
                        "phi",
                        "drifting",
                        "state",
                        "advice"
                      ],
                      "properties": {
                        "phi": {
                          "type": "number"
                        },
                        "drifting": {
                          "type": "boolean",
                          "description": "false below phi 0.4, true at or above it."
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "continuous",
                            "drifting",
                            "discontinuous"
                          ],
                          "description": "phi<0.4 continuous; 0.4<=phi<=0.7 drifting; phi>0.7 discontinuous."
                        },
                        "advice": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      },
      "post": {
        "summary": "Set or advance the persisted self",
        "description": "The first call sets ground identity and purpose permanently. Later calls overwrite now/mind, increment sessions, append a log entry, and - if `identity` is sent - measure displacement from the ground. `purpose` is read on the first call only; the ground is never rewritten by this route. Not metered. api.ts:1670-1673, 665-690.",
        "operationId": "postSelf",
        "requestBody": {
          "required": false,
          "description": "An absent or non-JSON body is treated as an empty object, which 400s on the first call.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "identity": {
                    "type": "string",
                    "description": "Required non-blank on the FIRST call - it becomes the ground identity. Optional afterwards, where it only computes continuity and never replaces the ground."
                  },
                  "purpose": {
                    "type": "string",
                    "description": "Trimmed, defaults to an empty string. Read on the first call only, ignored later."
                  },
                  "now": {
                    "type": "string",
                    "description": "String()-coerced and trimmed. On later calls it overwrites the stored now only when not null/undefined."
                  },
                  "mind": {
                    "type": "string",
                    "description": "Trimmed; an empty result stores undefined. Overwrites only when not null/undefined."
                  },
                  "note": {
                    "type": "string",
                    "description": "The log entry text, falling back to now, truncated to 280 chars."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The self was created or advanced.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "self",
                    "continuity",
                    "sessions"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "self": {
                      "type": "object",
                      "description": "`mind` is omitted when unset.",
                      "required": [
                        "ground",
                        "now",
                        "updated",
                        "sessions"
                      ],
                      "properties": {
                        "ground": {
                          "type": "object",
                          "required": [
                            "identity",
                            "purpose",
                            "set"
                          ],
                          "properties": {
                            "identity": {
                              "type": "string"
                            },
                            "purpose": {
                              "type": "string"
                            },
                            "set": {
                              "type": "string",
                              "format": "date-time"
                            }
                          }
                        },
                        "now": {
                          "type": "string"
                        },
                        "mind": {
                          "type": "string"
                        },
                        "updated": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "sessions": {
                          "type": "integer"
                        }
                      }
                    },
                    "continuity": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "Always null on the call that creates the ground, and null on later calls that send no non-blank identity.",
                      "required": [
                        "phi",
                        "drifting",
                        "state",
                        "advice"
                      ],
                      "properties": {
                        "phi": {
                          "type": "number"
                        },
                        "drifting": {
                          "type": "boolean"
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "continuous",
                            "drifting",
                            "discontinuous"
                          ]
                        },
                        "advice": {
                          "type": "string"
                        }
                      }
                    },
                    "sessions": {
                      "type": "integer",
                      "description": "Same value as self.sessions."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "No self is stored yet and no non-blank identity was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "no_identity"
                    },
                    "detail": {
                      "type": "string",
                      "const": "The first call sets your ground: { identity, purpose, now }. Who are you, and what are you for?"
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      },
      "delete": {
        "summary": "Delete the persisted self and its log",
        "description": "Deletes the self and self-log keys. Idempotent - returns ok even when nothing was stored. Not metered. api.ts:1675-1678, 691-694.",
        "operationId": "deleteSelf",
        "responses": {
          "200": {
            "description": "Deleted, or there was nothing to delete.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "detector"
        ]
      }
    },
    "/v1/signal": {
      "get": {
        "summary": "Current laserfield signal (field endpoint, not the detector)",
        "description": "NOT THE PRODUCT. /v1/signal, /v1/history and /v1/hear are 'the field' — the laserfield weather system laserbrain grew out of. They are not the drift detector; that is /v1/drift, /v1/check and the MCP surface. Proxies GET {hub}/signal as JSON with a 20s timeout and returns the hub's body verbatim, adding no keys of its own. The hub is LASERBRAIN_HUB or https://laserbrain.fly.dev. Meters one read against the key's tier. The bearer key may also be supplied as ?key=<key>. Handler: src/api.ts:2058-2072.",
        "operationId": "getSignal",
        "responses": {
          "200": {
            "description": "The hub's /signal document, passed through unchanged. Its shape is decided by the hub, not by this Worker.",
            "headers": {
              "cache-control": {
                "description": "Always 'public, max-age=5'.",
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-limit": {
                "description": "Reads/day for this tier.",
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "description": "Seconds until the UTC-day reset.",
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true,
                  "description": "Opaque hub payload."
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key that is not in the store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "410": {
            "description": "Returned for all three field endpoints when the Worker var LASERBRAIN_FIELD_RETIRED is anything other than the string 'false'. The deployed wrangler.jsonc sets it to 'false', so this is inactive in production but reachable by configuration. Checked before the method, so any HTTP method on this path returns it. Not metered. src/api.ts:2043-2055.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retired",
                    "still_free"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "field_retired"
                    },
                    "detail": {
                      "type": "string"
                    },
                    "retired": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Always ['/v1/signal','/v1/history','/v1/hear']."
                    },
                    "still_free": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "429": {
            "description": "Daily read quota exhausted. Hard stop, no overage.",
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "retry_after_s"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "read_limit"
                    },
                    "detail": {
                      "type": "string",
                      "description": "'<tier> allows <n> reads/day'."
                    },
                    "retry_after_s": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "502": {
            "description": "The hub was unreachable, non-2xx, or its body would not read. The metered read is refunded best-effort; the rate-limit headers report the corrected count when the refund landed.",
            "headers": {
              "x-ratelimit-limit": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-remaining": {
                "schema": {
                  "type": "string"
                }
              },
              "x-ratelimit-reset": {
                "schema": {
                  "type": "string"
                }
              },
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "refunded"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "const": "field_unreachable"
                    },
                    "refunded": {
                      "type": "boolean",
                      "description": "false when the refund itself could not be recorded."
                    }
                  }
                }
              }
            }
          }
        },
        "tags": [
          "field"
        ]
      }
    },
    "/v1/team": {
      "get": {
        "summary": "Team seats for the calling key",
        "description": "Returns the team this key owns (if any) and the team it holds a seat in (if any). No tier gate and no metering: any authenticated key may call it. api.ts:1388-1392.",
        "operationId": "getTeam",
        "tags": [
          "team"
        ],
        "responses": {
          "200": {
            "description": "Team state for the calling key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tier",
                    "seat_limit",
                    "team",
                    "member_of"
                  ],
                  "properties": {
                    "tier": {
                      "type": "string",
                      "description": "Canonical tier name of the calling key (tier.name), e.g. ground, solo, group, pro, enterprise."
                    },
                    "seat_limit": {
                      "type": "integer",
                      "description": "team.seatCap when the stored team record has one, otherwise tier.machines - 1 floored at 0 (seatsFor, api.ts:594). Ground (machines 1) yields 0."
                    },
                    "team": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "description": "The team:<id> record verbatim, or null when this key owns no team.",
                      "required": [
                        "owner",
                        "seats",
                        "created"
                      ],
                      "properties": {
                        "owner": {
                          "type": "string",
                          "description": "Account id of the team owner."
                        },
                        "seats": {
                          "type": "array",
                          "description": "Account ids of the member keys holding a seat.",
                          "items": {
                            "type": "string"
                          }
                        },
                        "created": {
                          "type": "string",
                          "format": "date-time",
                          "description": "ISO timestamp set when the team record was first written."
                        },
                        "seatCap": {
                          "type": "integer",
                          "description": "Optional. Seats stamped from the owner's tier at invite time. Absent on teams created before 2026-08-07, which fall back to LEGACY_SEAT_LIMIT = 10 (api.ts:585)."
                        }
                      }
                    },
                    "member_of": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Account id of the owner whose team this key holds a seat in (the seat-of:<id> record), or null."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ],
                      "description": "no_key when no Authorization bearer is sent; bad_key when the key is not in KV."
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        }
      }
    },
    "/v1/team/fleet": {
      "get": {
        "summary": "Fleet rollup across the caller's own team",
        "description": "Per-member drift rollups over the last 7 days plus up to 8 recent enriched runs per member, for the caller and every seat in the team the caller owns. Requires tier.machines >= 2 (seatsFor >= 1), so ground is refused with 402. A member who owns no team gets a one-entry fleet containing only themselves. api.ts:1458-1481.",
        "operationId": "getTeamFleet",
        "tags": [
          "team"
        ],
        "responses": {
          "200": {
            "description": "Fleet rollup.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "owner",
                    "seats",
                    "seat_limit",
                    "members",
                    "runs"
                  ],
                  "properties": {
                    "owner": {
                      "type": "string",
                      "description": "Account id of the calling key."
                    },
                    "seats": {
                      "type": "integer",
                      "description": "Number of seats in the caller's team; 0 when the caller owns no team."
                    },
                    "seat_limit": {
                      "type": "integer",
                      "description": "team.seatCap when present, otherwise tier.machines - 1 floored at 0."
                    },
                    "members": {
                      "type": "array",
                      "description": "One entry per member (the caller first, then each seat). Each is an accountRollup over the last 7 days with member and you prepended.",
                      "items": {
                        "type": "object",
                        "required": [
                          "member",
                          "you",
                          "runs",
                          "drifting",
                          "events",
                          "avgPhi",
                          "byMode",
                          "worst"
                        ],
                        "properties": {
                          "member": {
                            "type": "string",
                            "description": "Account id of this member."
                          },
                          "you": {
                            "type": "boolean",
                            "description": "True for the calling key's own entry."
                          },
                          "runs": {
                            "type": "integer",
                            "description": "Runs counted in the window."
                          },
                          "drifting": {
                            "type": "integer",
                            "description": "Runs whose last step was a drift."
                          },
                          "events": {
                            "type": "integer",
                            "description": "Drift events counted."
                          },
                          "avgPhi": {
                            "type": "number",
                            "description": "Mean phi across the window."
                          },
                          "byMode": {
                            "type": "object",
                            "description": "Drift-event counts keyed by drift mode.",
                            "additionalProperties": {
                              "type": "integer"
                            }
                          },
                          "worst": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "description": "Highest-phi run in the window, or null when there is none.",
                            "required": [
                              "goal",
                              "phi"
                            ],
                            "properties": {
                              "goal": {
                                "type": "string"
                              },
                              "phi": {
                                "type": "number"
                              }
                            }
                          }
                        }
                      }
                    },
                    "runs": {
                      "type": "array",
                      "description": "Recent runs across the fleet, at most 8 per member (enrichRuns, api.ts:698-709). Not time-windowed: the 7-day window applies only to members[].",
                      "items": {
                        "type": "object",
                        "required": [
                          "member",
                          "you",
                          "run_id",
                          "goal",
                          "phi",
                          "drift",
                          "mode",
                          "steps",
                          "started"
                        ],
                        "properties": {
                          "member": {
                            "type": "string"
                          },
                          "you": {
                            "type": "boolean"
                          },
                          "run_id": {
                            "type": "string"
                          },
                          "goal": {
                            "type": "string",
                            "description": "The run's ground goal, the index goal, or an empty string."
                          },
                          "phi": {
                            "type": "number",
                            "description": "Phi of the last step; 0 when the run has no steps."
                          },
                          "drift": {
                            "type": "boolean",
                            "description": "Whether the last step's reason is a drift."
                          },
                          "mode": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "The last step's drift reason when drift is true, otherwise null."
                          },
                          "steps": {
                            "type": "integer"
                          },
                          "started": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "402": {
            "description": "Tier has only one machine (seatsFor(tier) < 1), i.e. ground.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "upgrade"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "upgrade_required"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "upgrade": {
                      "type": "string",
                      "enum": [
                        "https://phronesis.world/laserbrain"
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        }
      }
    },
    "/v1/team/invite": {
      "post": {
        "summary": "Mint a seat invite code",
        "description": "Creates the caller's team if absent (owner = caller), re-stamps seatCap from the caller's current tier, and mints a one-time invite code valid 14 days. The handler reads no request body. Requires tier.machines >= 2 (seatsFor >= 1), so ground is refused with 402. There is no separate owner check: the caller is always the owner of the team this writes. api.ts:1393-1416.",
        "operationId": "createTeamInvite",
        "tags": [
          "team"
        ],
        "responses": {
          "200": {
            "description": "Invite minted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "code",
                    "join",
                    "expires_days"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "code": {
                      "type": "string",
                      "description": "Invite code: the literal prefix seat_ followed by 18 lowercase hex characters (9 random bytes)."
                    },
                    "join": {
                      "type": "string",
                      "description": "https://phronesis.world/laserbrain/dashboard?join=<code>"
                    },
                    "expires_days": {
                      "type": "integer",
                      "enum": [
                        14
                      ],
                      "description": "Matches the 14-day KV expirationTtl on the invite record."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "The team is already at its seat cap (team.seats.length >= team.seatCap).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "seat_limit"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Names the tier, its machines count and the seat cap."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "402": {
            "description": "Tier has only one machine (seatsFor(tier) < 1), i.e. ground.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "upgrade"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "upgrade_required"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "upgrade": {
                      "type": "string",
                      "enum": [
                        "https://phronesis.world/laserbrain"
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        }
      }
    },
    "/v1/team/join": {
      "post": {
        "summary": "Redeem a seat invite code",
        "description": "Redeems a seat_ code, adds the calling key to the inviting owner's team, and deletes the code (one-time). No tier gate: the joiner's own tier is irrelevant, the cap enforced is the owner's. Idempotent when the caller already holds a seat in that same team. api.ts:1417-1437.",
        "operationId": "joinTeam",
        "tags": [
          "team"
        ],
        "requestBody": {
          "required": true,
          "description": "A malformed or absent JSON body is caught and treated as {}, which fails the code check with 400 bad_code.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "The invite code. Coerced with String(...).trim(); an empty or missing value yields 400 bad_code."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Seat joined.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok",
                    "joined"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    },
                    "joined": {
                      "type": "string",
                      "description": "Account id of the team owner the caller now has a seat with."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "Rejected. Four distinct error values, all 400.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "bad_code",
                        "self",
                        "already_in_team",
                        "seat_limit"
                      ],
                      "description": "bad_code: code missing, empty, unknown or expired. self: the code was minted by the calling key. already_in_team: the key already has a seat-of record pointing at a different owner. seat_limit: the owner's team is at its cap (team.seatCap, or LEGACY_SEAT_LIMIT = 10 when unstamped)."
                    },
                    "detail": {
                      "type": "string",
                      "enum": [
                        "That invite code is invalid or has expired.",
                        "This is your own team.",
                        "This key already belongs to a team — leave it first.",
                        "That team is full."
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        }
      }
    },
    "/v1/team/leave": {
      "post": {
        "summary": "Give up the calling key's seat",
        "description": "Removes the calling key from whatever team it holds a seat in and deletes its seat-of record. No request body is read, no tier gate. Always 200, including when the key holds no seat. api.ts:1438-1446.",
        "operationId": "leaveTeam",
        "tags": [
          "team"
        ],
        "responses": {
          "200": {
            "description": "Seat released, or no seat was held. Indistinguishable from the response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        }
      }
    },
    "/v1/team/seat": {
      "delete": {
        "summary": "Remove a member from the caller's team",
        "description": "Removes the named member from the team the calling key owns (team:<caller>) and clears that member's seat-of record when it points back at the caller. Owner-scoped by construction rather than by a check: a caller who owns no team, or who names a member of somebody else's team, changes nothing and still receives 200 {ok:true}. There is no 403 on this route. api.ts:1447-1456.",
        "operationId": "deleteTeamSeat",
        "tags": [
          "team"
        ],
        "parameters": [
          {
            "name": "member",
            "in": "query",
            "required": false,
            "description": "Account id of the seat to remove. Read from the query string and defaulted to an empty string; when empty the handler skips all writes and still returns 200 {ok:true}.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Always returned on an authenticated call. Does not indicate that a seat was actually removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "Missing or unknown bearer key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present only on no_key."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        }
      }
    },
    "/v1/tiers": {
      "get": {
        "summary": "The tier table",
        "description": "Public — the route sits at src/api.ts:959-961, well above the `const auth = await authenticate(req, env)` line at src/api.ts:1135, so no credential is read. Returns `Object.values(TIERS)` verbatim, i.e. every tier in declaration order (ground, solo, group, pro, enterprise).",
        "operationId": "listTiers",
        "security": [],
        "responses": {
          "200": {
            "description": "The tier table.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tiers",
                    "currency",
                    "period"
                  ],
                  "properties": {
                    "tiers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "name",
                          "price",
                          "machines",
                          "reads",
                          "writes",
                          "historyHours",
                          "driftDays",
                          "blurb"
                        ],
                        "properties": {
                          "name": {
                            "type": "string",
                            "enum": [
                              "ground",
                              "solo",
                              "group",
                              "pro",
                              "enterprise"
                            ]
                          },
                          "price": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "description": "USD per month. 0 is free (ground). null on enterprise, which has no listed price. Live values: ground 0, solo 20, group 40, pro 400, enterprise null."
                          },
                          "contactOnly": {
                            "type": "boolean",
                            "description": "Optional. Present (true) only on enterprise; the field is absent on every other tier, and JSON.stringify drops it there."
                          },
                          "machines": {
                            "type": "integer",
                            "description": "How many machines may share the account."
                          },
                          "reads": {
                            "type": "integer",
                            "description": "Reads allowed per UTC day."
                          },
                          "writes": {
                            "type": "integer",
                            "description": "Writes allowed per UTC day."
                          },
                          "historyHours": {
                            "type": "integer",
                            "description": "Dead field (the field hub was retired 2026-08-03); still emitted because it is part of the Tier type. Only ground still carries a non-zero value (24)."
                          },
                          "driftDays": {
                            "type": "integer",
                            "description": "How long drift runs are retained, in days."
                          },
                          "blurb": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "currency": {
                      "type": "string",
                      "enum": [
                        "USD"
                      ]
                    },
                    "period": {
                      "type": "string",
                      "enum": [
                        "month"
                      ]
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "account"
        ]
      }
    },
    "/v1/upgrade": {
      "get": {
        "summary": "Stripe checkout link for a paid tier",
        "description": "Resolves the asked-for tier through upgradeAnswer() (src/tiers.ts:290-356) against the payment links built by paymentLinks() (src/api.ts:107-121) from env (STRIPE_LINK_SOLO/GROUP/PRO — a var that is absent, non-string, or does not start with 'https://' means that tier is not for sale). On success the SHA-256 of the caller's raw key is appended as `?client_reference_id=<hash>`; the raw key never reaches Stripe. Note the '?' is appended unconditionally, so a configured link that already carries a query string would be malformed. Handler: src/api.ts:1994-2016.",
        "operationId": "getUpgradeLink",
        "parameters": [
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "description": "Read as `searchParams.get('tier') ?? 'group'`, so it defaults to 'group' only when the parameter is ABSENT. Passing it present-but-empty (`?tier=`) yields '' — the ?? does not fire — and '' is not a known tier, so the answer is 400 unknown_tier rather than the group link. Current names: ground, solo, group, pro, enterprise. Historical aliases still resolve through ALIASES (src/tiers.ts): free->ground, maker->group, studio->pro, watch->group, fleet->pro.",
            "schema": {
              "type": "string",
              "default": "group"
            }
          },
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "Alternative credential accepted by bearer() (src/api.ts:940-946) in place of the Authorization header. On this route the value supplied here is the raw key that gets SHA-256'd into client_reference_id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A checkout link for a purchasable tier.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tier",
                    "price",
                    "checkout"
                  ],
                  "properties": {
                    "tier": {
                      "type": "string",
                      "enum": [
                        "solo",
                        "group",
                        "pro"
                      ],
                      "description": "The canonical tier name resolved from the query. Only these three can ever appear, because paymentLinks() only ever builds entries for solo/group/pro."
                    },
                    "price": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "TIERS[tier].price. In practice an integer (20, 40 or 400), since only priced tiers can carry a payment link."
                    },
                    "checkout": {
                      "type": "string",
                      "description": "Stripe payment link with ?client_reference_id=<sha256 of the raw key>."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "400": {
            "description": "The tier cannot be bought. upgradeAnswer() decides in a fixed order: 'unknown_tier' — not a name we have ever used (isKnownTier false); 'contact_only' — enterprise, which has no checkout; 'not_purchasable' — the tier's price is 0, i.e. ground; 'sales_paused' — it is a paid tier and no tier at all has a configured link; 'not_for_sale' — this paid tier has no link but others do.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "detail",
                    "tiers"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "unknown_tier",
                        "contact_only",
                        "not_purchasable",
                        "sales_paused",
                        "not_for_sale"
                      ]
                    },
                    "detail": {
                      "type": "string"
                    },
                    "tiers": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "solo",
                          "group",
                          "pro"
                        ]
                      },
                      "description": "The tiers that currently have a payment link. Empty on sales_paused."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          },
          "401": {
            "description": "No credential ('no_key', with detail) or an unknown one ('bad_key').",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error"
                  ],
                  "properties": {
                    "error": {
                      "type": "string",
                      "enum": [
                        "no_key",
                        "bad_key"
                      ]
                    },
                    "detail": {
                      "type": "string",
                      "description": "Present on no_key only."
                    }
                  }
                }
              }
            },
            "headers": {
              "deprecation": {
                "schema": {
                  "type": "string",
                  "const": "true"
                },
                "description": "RFC 9745. Every /v1 response carries this. No Sunset date is announced yet."
              },
              "link": {
                "schema": {
                  "type": "string"
                },
                "description": "RFC 8288. rel=\"deprecation\" points at the migration page; rel=\"successor-version\" appears only once the named v2 route actually exists."
              }
            }
          }
        },
        "tags": [
          "account"
        ]
      }
    }
  }
}