{
  "openapi": "3.1.0",
  "info": {
    "title": "cheffry-s-dungeon",
    "version": "0.1.0",
    "description": "A low-poly 3D dungeon you cook through with friends: a live hub, four-chef parties, melee with dodge, and one seeded dungeon a day with a leaderboard and ghosts to race."
  },
  "paths": {
    "/api/notes": {
      "get": {
        "summary": "The notes, newest first.",
        "operationId": "getApiNotes",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The notes, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Note"
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "note",
          "verb": "list",
          "least": "anon"
        }
      },
      "post": {
        "summary": "Add a note as the person asking.",
        "operationId": "postApiNotes",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "body"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Add a note as the person asking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "note",
          "verb": "create",
          "least": "user"
        }
      }
    },
    "/api/notes/{id}": {
      "get": {
        "summary": "One note.",
        "operationId": "getApiNotesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One note.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "note",
          "verb": "read",
          "least": "anon"
        }
      },
      "patch": {
        "summary": "Change a note's words.",
        "operationId": "patchApiNotesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "body"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Change a note's words.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Note"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "note",
          "verb": "update",
          "least": "author"
        }
      },
      "delete": {
        "summary": "Remove a note.",
        "operationId": "deleteApiNotesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Remove a note.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "note",
          "verb": "delete",
          "least": "author"
        }
      }
    },
    "/api/avatars": {
      "get": {
        "summary": "Every chef in the hub.",
        "operationId": "getApiAvatars",
        "responses": {
          "200": {
            "description": "Every chef in the hub.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Avatar"
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "avatar",
          "verb": "list",
          "least": "anon"
        }
      },
      "post": {
        "summary": "Step into the hub as yourself (already there, you stay put).",
        "operationId": "postApiAvatars",
        "responses": {
          "201": {
            "description": "Step into the hub as yourself (already there, you stay put).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Avatar"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "avatar",
          "verb": "create",
          "least": "user"
        }
      }
    },
    "/api/avatars/{id}": {
      "get": {
        "summary": "One chef in the hub.",
        "operationId": "getApiAvatarsId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One chef in the hub.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Avatar"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "avatar",
          "verb": "read",
          "least": "anon"
        }
      },
      "patch": {
        "summary": "Walk somewhere, emote, or say something.",
        "operationId": "patchApiAvatarsId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Any of: where to walk (x and z together), an emote, words.",
                "properties": {
                  "x": {
                    "type": "number"
                  },
                  "z": {
                    "type": "number"
                  },
                  "emote": {
                    "enum": [
                      "wave",
                      "dance",
                      "cheer",
                      "taste",
                      "bow"
                    ]
                  },
                  "say": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 140
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Walk somewhere, emote, or say something.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Avatar"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "avatar",
          "verb": "update",
          "least": "author"
        }
      },
      "delete": {
        "summary": "Leave the hub.",
        "operationId": "deleteApiAvatarsId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Leave the hub.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "avatar",
          "verb": "delete",
          "least": "author"
        }
      }
    },
    "/api/delves": {
      "get": {
        "summary": "The tables, newest first.",
        "operationId": "getApiDelves",
        "responses": {
          "200": {
            "description": "The tables, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Delve"
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "delve",
          "verb": "list",
          "least": "anon"
        }
      },
      "post": {
        "summary": "Open today's dungeon with yourself at the table.",
        "operationId": "postApiDelves",
        "responses": {
          "201": {
            "description": "Open today's dungeon with yourself at the table.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delve"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "delve",
          "verb": "create",
          "least": "user"
        }
      }
    },
    "/api/delves/{id}": {
      "get": {
        "summary": "One table. Its ticks stream at /delves/{id}/events.",
        "operationId": "getApiDelvesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One table. Its ticks stream at /delves/{id}/events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delve"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "delve",
          "verb": "read",
          "least": "anon"
        }
      }
    },
    "/api/heroes": {
      "get": {
        "summary": "Every chef at every table.",
        "operationId": "getApiHeroes",
        "responses": {
          "200": {
            "description": "Every chef at every table.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Hero"
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "hero",
          "verb": "list",
          "least": "anon"
        }
      },
      "post": {
        "summary": "Take a seat at a table (four a table).",
        "operationId": "postApiHeroes",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "delve": {
                    "type": "integer"
                  },
                  "class": {
                    "type": "string"
                  }
                },
                "required": [
                  "delve"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Take a seat at a table (four a table).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hero"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "hero",
          "verb": "create",
          "least": "user"
        }
      }
    },
    "/api/heroes/{id}": {
      "get": {
        "summary": "One chef at a table.",
        "operationId": "getApiHeroesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One chef at a table.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hero"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "hero",
          "verb": "read",
          "least": "anon"
        }
      },
      "patch": {
        "summary": "Give your chef an order.",
        "operationId": "patchApiHeroesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "move: walk to (x, z). attack: fight `target` (a monster's id), or swing where you face. dodge: roll toward (x, z). ability: your class's signature move. throw: lob a carried `spice` toward (x, z). stop.",
                "properties": {
                  "type": {
                    "enum": [
                      "move",
                      "attack",
                      "dodge",
                      "ability",
                      "throw",
                      "stop"
                    ]
                  },
                  "x": {
                    "type": "number"
                  },
                  "z": {
                    "type": "number"
                  },
                  "target": {
                    "type": "integer"
                  },
                  "spice": {
                    "enum": [
                      "pepper-bomb",
                      "salt-the-earth",
                      "hot-sauce"
                    ]
                  }
                },
                "required": [
                  "type"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Give your chef an order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Hero"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "hero",
          "verb": "update",
          "least": "author"
        }
      },
      "delete": {
        "summary": "Leave the table. The last chef out ends the delve.",
        "operationId": "deleteApiHeroesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Leave the table. The last chef out ends the delve.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "hero",
          "verb": "delete",
          "least": "author"
        }
      }
    },
    "/api/pantry": {
      "get": {
        "summary": "Your pantry: the ingredients you have brought home.",
        "operationId": "getApiPantry",
        "responses": {
          "200": {
            "description": "Your pantry: the ingredients you have brought home.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "description": "How much of one ingredient a chef has brought home.",
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "author_id": {
                        "type": "string"
                      },
                      "ingredient": {
                        "enum": [
                          "ghost-pepper",
                          "slime-jelly",
                          "stale-crust",
                          "rock-salt",
                          "torch-mushroom"
                        ]
                      },
                      "count": {
                        "type": "integer",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "id",
                      "author_id",
                      "ingredient",
                      "count"
                    ]
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "pantry",
          "verb": "list",
          "least": "author"
        }
      }
    },
    "/api/pantry/{id}": {
      "get": {
        "summary": "One ingredient in your pantry.",
        "operationId": "getApiPantryId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One ingredient in your pantry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "How much of one ingredient a chef has brought home.",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "author_id": {
                      "type": "string"
                    },
                    "ingredient": {
                      "enum": [
                        "ghost-pepper",
                        "slime-jelly",
                        "stale-crust",
                        "rock-salt",
                        "torch-mushroom"
                      ]
                    },
                    "count": {
                      "type": "integer",
                      "minimum": 0
                    }
                  },
                  "required": [
                    "id",
                    "author_id",
                    "ingredient",
                    "count"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "pantry",
          "verb": "read",
          "least": "author"
        }
      }
    },
    "/api/spices": {
      "get": {
        "summary": "Your shelf: the jars you have crafted and not thrown.",
        "operationId": "getApiSpices",
        "responses": {
          "200": {
            "description": "Your shelf: the jars you have crafted and not thrown.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "description": "A crafted jar on a chef's shelf, until it is thrown.",
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "kind": {
                        "enum": [
                          "pepper-bomb",
                          "salt-the-earth",
                          "hot-sauce"
                        ]
                      },
                      "author_id": {
                        "type": "string"
                      },
                      "author_name": {
                        "type": "string"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    },
                    "required": [
                      "id",
                      "kind",
                      "author_id",
                      "author_name",
                      "created_at"
                    ]
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "spice",
          "verb": "list",
          "least": "author"
        }
      },
      "post": {
        "summary": "Craft a spice: its recipe comes out of your pantry.",
        "operationId": "postApiSpices",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "spice": {
                    "enum": [
                      "pepper-bomb",
                      "salt-the-earth",
                      "hot-sauce"
                    ]
                  }
                },
                "required": [
                  "spice"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Craft a spice: its recipe comes out of your pantry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "A crafted jar on a chef's shelf, until it is thrown.",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "kind": {
                      "enum": [
                        "pepper-bomb",
                        "salt-the-earth",
                        "hot-sauce"
                      ]
                    },
                    "author_id": {
                      "type": "string"
                    },
                    "author_name": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "kind",
                    "author_id",
                    "author_name",
                    "created_at"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "spice",
          "verb": "create",
          "least": "user"
        }
      }
    },
    "/api/spices/{id}": {
      "get": {
        "summary": "One jar on your shelf.",
        "operationId": "getApiSpicesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One jar on your shelf.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "A crafted jar on a chef's shelf, until it is thrown.",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "kind": {
                      "enum": [
                        "pepper-bomb",
                        "salt-the-earth",
                        "hot-sauce"
                      ]
                    },
                    "author_id": {
                      "type": "string"
                    },
                    "author_name": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "kind",
                    "author_id",
                    "author_name",
                    "created_at"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "spice",
          "verb": "read",
          "least": "author"
        }
      },
      "delete": {
        "summary": "Tip a jar out.",
        "operationId": "deleteApiSpicesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tip a jar out.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "spice",
          "verb": "delete",
          "least": "author"
        }
      }
    },
    "/api/wallets": {
      "get": {
        "summary": "The Bread Barons: the biggest hoards of bread, biggest first.",
        "operationId": "getApiWallets",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Bread Barons: the biggest hoards of bread, biggest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "description": "A chef's bread, as the Bread Barons board shows it.",
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "author_id": {
                        "type": "string"
                      },
                      "author_name": {
                        "type": "string"
                      },
                      "loaves": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "updated_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    },
                    "required": [
                      "id",
                      "author_id",
                      "author_name",
                      "loaves",
                      "updated_at"
                    ]
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "wallet",
          "verb": "list",
          "least": "anon"
        }
      }
    },
    "/api/wallets/{id}": {
      "get": {
        "summary": "One chef's bread.",
        "operationId": "getApiWalletsId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One chef's bread.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "A chef's bread, as the Bread Barons board shows it.",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "author_id": {
                      "type": "string"
                    },
                    "author_name": {
                      "type": "string"
                    },
                    "loaves": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "author_id",
                    "author_name",
                    "loaves",
                    "updated_at"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "wallet",
          "verb": "read",
          "least": "anon"
        }
      }
    },
    "/api/trades": {
      "get": {
        "summary": "Your ledger: every loaf in or out of your wallet, newest first.",
        "operationId": "getApiTrades",
        "responses": {
          "200": {
            "description": "Your ledger: every loaf in or out of your wallet, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "description": "Loaves in or out of a wallet: an ingredient sold, a jar bought, or a delve's earnings. `loaves` is signed.",
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "author_id": {
                        "type": "string"
                      },
                      "author_name": {
                        "type": "string"
                      },
                      "kind": {
                        "enum": [
                          "sell",
                          "buy",
                          "earn"
                        ]
                      },
                      "item": {
                        "type": "string"
                      },
                      "count": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "loaves": {
                        "type": "integer"
                      },
                      "note": {
                        "type": "string"
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    },
                    "required": [
                      "id",
                      "author_id",
                      "author_name",
                      "kind",
                      "item",
                      "count",
                      "loaves",
                      "note",
                      "created_at"
                    ]
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "trade",
          "verb": "list",
          "least": "author"
        }
      },
      "post": {
        "summary": "Trade at the Bread Exchange: sell pantry ingredients for loaves, or buy a spice jar with them.",
        "operationId": "postApiTrades",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "sell: `count` (1 by default) of a pantry `ingredient` for loaves. buy: one jar of a `spice` for loaves.",
                "properties": {
                  "kind": {
                    "enum": [
                      "sell",
                      "buy"
                    ]
                  },
                  "ingredient": {
                    "enum": [
                      "ghost-pepper",
                      "slime-jelly",
                      "stale-crust",
                      "rock-salt",
                      "torch-mushroom"
                    ]
                  },
                  "count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 30,
                    "default": 1
                  },
                  "spice": {
                    "enum": [
                      "pepper-bomb",
                      "salt-the-earth",
                      "hot-sauce"
                    ]
                  }
                },
                "required": [
                  "kind"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Trade at the Bread Exchange: sell pantry ingredients for loaves, or buy a spice jar with them.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Loaves in or out of a wallet: an ingredient sold, a jar bought, or a delve's earnings. `loaves` is signed.",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "author_id": {
                      "type": "string"
                    },
                    "author_name": {
                      "type": "string"
                    },
                    "kind": {
                      "enum": [
                        "sell",
                        "buy",
                        "earn"
                      ]
                    },
                    "item": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "loaves": {
                      "type": "integer"
                    },
                    "note": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "author_id",
                    "author_name",
                    "kind",
                    "item",
                    "count",
                    "loaves",
                    "note",
                    "created_at"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "trade",
          "verb": "create",
          "least": "user"
        }
      }
    },
    "/api/trades/{id}": {
      "get": {
        "summary": "One line of your ledger.",
        "operationId": "getApiTradesId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One line of your ledger.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Loaves in or out of a wallet: an ingredient sold, a jar bought, or a delve's earnings. `loaves` is signed.",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "author_id": {
                      "type": "string"
                    },
                    "author_name": {
                      "type": "string"
                    },
                    "kind": {
                      "enum": [
                        "sell",
                        "buy",
                        "earn"
                      ]
                    },
                    "item": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "loaves": {
                      "type": "integer"
                    },
                    "note": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "required": [
                    "id",
                    "author_id",
                    "author_name",
                    "kind",
                    "item",
                    "count",
                    "loaves",
                    "note",
                    "created_at"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "trade",
          "verb": "read",
          "least": "author"
        }
      }
    },
    "/api/runs": {
      "get": {
        "summary": "A day's leaderboard (today unless `day`), best first.",
        "operationId": "getApiRuns",
        "parameters": [
          {
            "name": "day",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A day's leaderboard (today unless `day`), best first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Run"
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "run",
          "verb": "list",
          "least": "anon"
        }
      }
    },
    "/api/runs/{id}": {
      "get": {
        "summary": "One run, with its ghost.",
        "operationId": "getApiRunsId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One run, with its ghost.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Run"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "run",
          "verb": "read",
          "least": "anon"
        }
      },
      "delete": {
        "summary": "Strike a run from the board.",
        "operationId": "deleteApiRunsId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Strike a run from the board.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "run",
          "verb": "delete",
          "least": "admin"
        }
      }
    },
    "/api/audit": {
      "get": {
        "summary": "The audit trail, newest first; `before` pages back by id.",
        "operationId": "getApiAudit",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The audit trail, newest first; `before` pages back by id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AuditRow"
                  }
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "audit",
          "verb": "list",
          "least": "admin"
        }
      }
    },
    "/api/audit/{id}": {
      "get": {
        "summary": "One row of the audit trail.",
        "operationId": "getApiAuditId",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One row of the audit trail.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditRow"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-contract": {
          "resource": "audit",
          "verb": "read",
          "least": "admin"
        }
      }
    },
    "/mcp": {
      "post": {
        "summary": "One JSON-RPC 2.0 message to the MCP server; its tools are the actions.",
        "operationId": "postMcp",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "jsonrpc": {
                    "const": "2.0"
                  },
                  "id": {},
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  }
                },
                "required": [
                  "jsonrpc",
                  "method"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One JSON-RPC 2.0 message to the MCP server; its tools are the actions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/healthz": {
      "get": {
        "summary": "Whether the app is up: its version, commit, environment and seconds since it started.",
        "operationId": "getHealthz",
        "responses": {
          "200": {
            "description": "Whether the app is up: its version, commit, environment and seconds since it started.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "version": {
                      "type": "string"
                    },
                    "commit": {
                      "type": "string"
                    },
                    "env": {
                      "type": "string"
                    },
                    "uptime": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "ok",
                    "version",
                    "uptime"
                  ]
                }
              }
            }
          },
          "default": {
            "description": "{ error, message }: why not",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Note": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "author_id": {
            "type": "string"
          },
          "author_name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "body",
          "author_id",
          "author_name",
          "created_at",
          "updated_at"
        ]
      },
      "Avatar": {
        "type": "object",
        "description": "A chef in the hub: walking from (x, z) to (tx, tz), set off `age` ms ago at the hub's speed.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "author_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "x": {
            "type": "number"
          },
          "z": {
            "type": "number"
          },
          "tx": {
            "type": "number"
          },
          "tz": {
            "type": "number"
          },
          "age": {
            "type": "integer"
          },
          "emote": {
            "type": [
              "string",
              "null"
            ]
          },
          "emoteAge": {
            "type": "integer"
          },
          "say": {
            "type": [
              "string",
              "null"
            ]
          },
          "sayAge": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "author_id",
          "name",
          "x",
          "z",
          "tx",
          "tz",
          "age"
        ]
      },
      "Delve": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "day": {
            "type": "string",
            "format": "date"
          },
          "author_id": {
            "type": "string"
          },
          "host_name": {
            "type": "string"
          },
          "party": {
            "type": "array",
            "maxItems": 4,
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "author_id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "classId": {
                  "type": "string"
                }
              }
            }
          },
          "depth": {
            "type": "integer"
          },
          "rooms": {
            "type": "integer"
          },
          "room": {
            "type": "string"
          },
          "score": {
            "type": "integer"
          },
          "over": {
            "enum": [
              null,
              "victory",
              "wiped",
              "timeout",
              "abandoned"
            ]
          },
          "joinable": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "day",
          "author_id",
          "host_name",
          "party",
          "depth",
          "rooms",
          "room",
          "score",
          "over",
          "joinable"
        ]
      },
      "Hero": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "delve_id": {
            "type": "integer"
          },
          "author_id": {
            "type": "string"
          },
          "author_name": {
            "type": "string"
          },
          "classId": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "delve_id",
          "author_id",
          "author_name",
          "classId"
        ]
      },
      "Run": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "day": {
            "type": "string",
            "format": "date"
          },
          "seed": {
            "type": "integer"
          },
          "outcome": {
            "enum": [
              "victory",
              "wiped",
              "timeout",
              "abandoned"
            ]
          },
          "score": {
            "type": "integer"
          },
          "depth": {
            "type": "integer"
          },
          "kills": {
            "type": "integer"
          },
          "gold": {
            "type": "integer"
          },
          "ticks": {
            "type": "integer"
          },
          "crew": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "author_id": {
            "type": "string"
          },
          "author_name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "ghost": {
            "type": "string",
            "description": "Where the lead chef was every 10 ticks: `floor,x*10,z*10;…`. Only on a single run."
          }
        },
        "required": [
          "id",
          "day",
          "seed",
          "outcome",
          "score",
          "depth",
          "kills",
          "gold",
          "ticks",
          "crew",
          "author_id",
          "author_name",
          "created_at"
        ]
      },
      "AuditRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "who_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "who_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "enum": [
              "anon",
              "user",
              "admin"
            ]
          },
          "resource": {
            "type": "string"
          },
          "verb": {
            "type": "string"
          },
          "target": {
            "type": [
              "string",
              "null"
            ]
          },
          "allowed": {
            "enum": [
              0,
              1
            ]
          },
          "outcome": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "at",
          "role",
          "resource",
          "verb",
          "allowed",
          "outcome"
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
