{
  "openapi": "3.1.0",
  "info": {
    "title": "zpe-chat — the universe's one messaging API",
    "version": "1.1.1",
    "description": "Conversations with members, attachable to any object. Every row is scoped to the caller's account, and a message's sender is the session's principal rather than a field a caller can write."
  },
  "servers": [
    {
      "url": "/chat",
      "description": "zpe-chat on zpe.vc"
    }
  ],
  "paths": {
    "/_/api/v1/conversations": {
      "get": {
        "operationId": "getConversations",
        "summary": "The conversations this caller holds a seat in, newest first.",
        "tags": [
          "conversations"
        ],
        "parameters": [
          {
            "name": "object_kind",
            "in": "query",
            "description": "With `object_id`: the conversations about one thing, for a caller that already knows what it is asking for. One without the other is refused.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "object_id",
            "in": "query",
            "description": "The object's id in its own system. Asked with `object_kind`, and repeated on every page: `next_cursor` carries the position, not the filter.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "how many conversations one page carries: 1 to 200, and 50 when the caller does not say",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "the `next_cursor` a previous page answered; the page after that one. Absent, the listing starts at the newest conversation.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's conversations, newest first, one page at a time. `next_cursor` is the value to send as `cursor` for the page after this one, and null when this page holds the last conversation.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "conversations",
                    "next_cursor"
                  ],
                  "properties": {
                    "conversations": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "the `cursor` that continues after this page; null when this page holds the last conversation"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` is not a whole number in 1 to 200, or `cursor` was not written by this app.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no key, so there is nobody whose conversations these could be.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The key is real and does not carry this route's grant or scope, so the door refused it before the route ran.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "This key has spent its request ceiling for the moment; retry once the answer's wait has passed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "This app failed while answering",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The door or the key store did not answer, so the credential could not be checked and nothing was listed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "post": {
        "operationId": "postConversations",
        "summary": "Create a conversation, with the caller as its owner.",
        "tags": [
          "conversations"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "the caller's own retry handle. The same key created twice writes one conversation and answers the conversation id the first call answered; no display number is spent on the replay.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The conversation to make. `kind` decides the retention default: an `object` conversation keeps its bodies for 30 days, `direct` and `group` keep them while the conversation is open.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind"
                ],
                "additionalProperties": false,
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "direct",
                      "group",
                      "object",
                      "public"
                    ]
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "object_kind": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "What this conversation is about, when kind is `object`."
                  },
                  "object_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "retention_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "description": "Null keeps the bodies; an object conversation defaults to 30."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "That `Idempotency-Key` was already here: nothing was written, no display number was spent, and the conversation is the one the first call answered with.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "conversation"
                  ],
                  "properties": {
                    "conversation": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "A conversation this call created: body `{kind, object_kind?, object_id?, title?}`. Its `display_id` is this account's next number, and the caller's owner seat is written in the same batch, so the thread is readable by the person who made it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "conversation"
                  ],
                  "properties": {
                    "conversation": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is not one JSON object, `kind` is not one of the four, an optional field is not text, or the `Idempotency-Key` header is empty.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no key, so there is nobody to own the conversation.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "This account may not do that",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Nothing is here",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/_/api/v1/conversations/{id}": {
      "get": {
        "operationId": "getConversationsById",
        "summary": "One conversation, its members, and a page of its messages.",
        "tags": [
          "conversations"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the conversation to read, as `conversation.id` spells it",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "the lowest message number to include; `next_cursor` from a previous page is exactly this value",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "exclusive upper bound: messages numbered below this are included, this one is not",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "how many messages one page carries: 1 to 200, and 50 when the caller does not say",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The conversation, its members, and the page of its messages: ascending by `seq`, `since` inclusive and `before` exclusive. `next_cursor` is the `since` that continues after this page, and null when there is no more.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "conversation",
                    "members",
                    "messages",
                    "next_cursor",
                    "unread"
                  ],
                  "properties": {
                    "conversation": {
                      "type": "object"
                    },
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "messages": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "next_cursor": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "the `since` that continues after this page; null when the page holds the last message"
                    },
                    "unread": {
                      "type": "integer",
                      "description": "messages this caller has not read, by the spec's arithmetic"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A query value is not a whole number, or `limit` is outside 1 to 200.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no key, so there is nobody to read as.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The key is real and does not carry this route's grant or scope, so the door refused it before the route ran.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No conversation this caller holds a seat in answers to that id. Never distinguished from one that does not exist.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "This key has spent its request ceiling for the moment; retry once the answer's wait has passed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "This app failed while answering",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The door or the key store did not answer, so the credential could not be checked and nothing was read.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      },
      "patch": {
        "operationId": "patchConversationsById",
        "summary": "Open or close a conversation. Its owner is the only caller who may.",
        "tags": [
          "conversations"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the conversation whose state is being set",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The state to move to. Nothing else on a conversation is editable.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "state"
                ],
                "additionalProperties": false,
                "properties": {
                  "state": {
                    "type": "string",
                    "enum": [
                      "open",
                      "closed"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The conversation with `state` set as asked: body `{\"state\": \"open\" | \"closed\"}`. A closed conversation refuses new messages with `closed` until its owner reopens it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "conversation"
                  ],
                  "properties": {
                    "conversation": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is not one JSON object, or `state` is neither `open` nor `closed`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no key, so there is nobody whose ownership could be checked.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The caller holds a seat in this conversation but is not its owner, or the key does not carry this route's grant or scope.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No conversation this caller holds a seat in answers to that id. Never distinguished from one that does not exist.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "This key has spent its request ceiling for the moment; retry once the answer's wait has passed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "This app failed while answering",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The door or the key store did not answer, so the credential could not be checked and no state was changed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/_/api/v1/conversations/{id}/messages": {
      "post": {
        "operationId": "postConversationsByIdMessages",
        "summary": "Post a message into a conversation, under a number this conversation has not used.",
        "tags": [
          "messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the conversation to post into, as `conversation.id` spells it",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "the caller's own retry handle. The same key posted twice writes one row and answers the message id the first call answered.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "One message. `Idempotency-Key` is a header, not a field: a retry with the same key answers the row it already made.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "body"
                ],
                "additionalProperties": false,
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "app",
                      "upwork",
                      "email"
                    ],
                    "description": "Where the message came from. Defaults to `app`. A session may not set it: only a credential can say a message arrived from somewhere other than this app."
                  },
                  "source_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "The id in that system, for a message that arrived from one."
                  },
                  "now": {
                    "type": "integer",
                    "minimum": 1,
                    "description": "The minute the source recorded the message, in epoch milliseconds. Absent means this app's clock. A credential sets it when it mirrors a line said somewhere else — the same rule `source` follows — and a value past this server's clock is refused. A replayed `Idempotency-Key` that names a different minute corrects the row it holds."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "That `Idempotency-Key` was already here: nothing was written, no number was spent, and the row is the one the first call answered with.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "A message this call wrote: its `seq` is the conversation's next number, and its `sender_principal` is the session's principal — never a value from the body.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is not one JSON object, carries no `body`, or the `Idempotency-Key` header is empty.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no key, so there is nobody to sign the message.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The caller is signed in and is not a member of this conversation.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No conversation in the caller's account holds that id. Never distinguished from one that does not exist.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The conversation is `closed`. Its owner can reopen it with `PATCH`; nobody can post into it.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "The body is past the 4,000-character ceiling: `too-long`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/_/api/v1/conversations/{id}/members": {
      "post": {
        "operationId": "postConversationsByIdMembers",
        "summary": "Add a principal to a conversation. A `direct` conversation takes exactly one other person.",
        "tags": [
          "members"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the conversation the seat is being written into",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "the caller's own retry handle. The same key twice answers the seat the first call wrote, and a key this conversation already holds is never spent on a second person.",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The principal to seat. Its `role` defaults to `member`.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "principal"
                ],
                "additionalProperties": false,
                "properties": {
                  "principal": {
                    "type": "string",
                    "minLength": 1,
                    "description": "A principal the door names."
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "member"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Nothing new was written: either that principal already held a seat here, or this `Idempotency-Key` already names a seat — and then the answer is that seat, whichever principal the body names. A seat that had `left_at` set is live again.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "member"
                  ],
                  "properties": {
                    "member": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "A seat this call created: body `{\"principal_id\": \"…\"}`. The principal is now a member of this conversation's account, and may read and post in it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "member"
                  ],
                  "properties": {
                    "member": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body names no `principal_id`, the conversation is `direct` and already has both its people, or the `Idempotency-Key` header is empty.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no key, so there is nobody to add.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "This account may not do that",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No conversation this caller holds a seat in answers to that id. Never distinguished from one that does not exist.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/_/api/v1/conversations/{id}/read": {
      "post": {
        "operationId": "postConversationsByIdRead",
        "summary": "Move this caller's read watermark forward to a message number.",
        "tags": [
          "conversations"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the conversation whose watermark is being moved",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The message number this caller has read to. The watermark only ever advances: a lower number is accepted and changes nothing.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "seq"
                ],
                "additionalProperties": false,
                "properties": {
                  "seq": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The watermark this caller now holds, read back from the row. A `seq` below it leaves it unchanged: the answer is the value storage holds, never the value that was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "read_seq",
                    "unread"
                  ],
                  "properties": {
                    "read_seq": {
                      "type": "integer",
                      "description": "the highest message number this caller has read"
                    },
                    "unread": {
                      "type": "integer",
                      "description": "messages this caller has not read, by the spec's arithmetic"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is not `{seq}`, `seq` is not a whole number, or `seq` is past the last number this conversation has handed out.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no key, so there is nobody whose watermark could move.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The key is real and does not carry this route's grant or scope, so the door refused it before the route ran.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No conversation this caller holds a seat in answers to that id. Never distinguished from one that does not exist.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "This key has spent its request ceiling for the moment; retry once the answer's wait has passed.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "This app failed while answering",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "The door or the key store did not answer, so the credential could not be checked and no watermark moved.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    },
    "/_/api/v1/conversations/{id}/messages/{message_id}": {
      "delete": {
        "operationId": "deleteConversationsByIdMessagesById",
        "summary": "Delete one message. Its sender or the conversation's owner may.",
        "tags": [
          "messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "description": "the conversation the message is in, as `conversation.id` spells it",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "message_id",
            "in": "path",
            "description": "the message to remove, as `message.id` spells it",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "The row is gone. A deletion here is real: nothing marks the gap, `seq` is not reused, and \"this was said\" cannot be shown afterwards."
          },
          "400": {
            "description": "The request could not be read",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no key, so there is nobody whose authority could be checked.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "The caller holds a seat here but is neither the conversation's owner nor the message's sender.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No conversation this caller holds a seat in answers to that id, or no such message is in it. The two are never distinguished.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key minted on the account's settings page at zpe.vc/home/settings. Send it as `Authorization: Bearer <key>`."
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem+json: `type`, `title`, `status`, `code` and `instance`, plus `detail` when a person should read a sentence.",
        "required": [
          "type",
          "title",
          "status",
          "code",
          "instance"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "string",
            "enum": [
              "bad-request",
              "unauthenticated",
              "malformed",
              "unknown",
              "mismatch",
              "revoked",
              "expired",
              "another-account",
              "scope-refused",
              "grant-refused",
              "forbidden",
              "not-found",
              "method-not-allowed",
              "conflict",
              "gone",
              "payload-too-large",
              "unsupported-media-type",
              "unprocessable",
              "rate-limited",
              "payment-required",
              "not-open",
              "internal",
              "unavailable"
            ]
          },
          "instance": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          }
        }
      }
    }
  },
  "x-surface": {
    "app": "zpe-chat",
    "base": "/_/api/v1",
    "document": "/_/api/v1/openapi.json",
    "generated_by": "zpe-kit machineDocument"
  },
  "x-zpe-routes": 8,
  "x-zpe-documented": 8,
  "x-zpe-internal": []
}
