{
  "openapi": "3.1.0",
  "info": {
    "title": "BatonDeck API",
    "version": "1",
    "description": "The API the BatonDeck portal is built on, and the one a program talks to.\n\nTwo credentials. A browser sends the portal session cookie and, on anything that changes state, must be same-origin. A program sends `Authorization: Bearer <key>` — a workspace API key, minted in the portal, shown once. A key is never wider than the owner who minted it: its scopes narrow their permissions and can never widen them.\n\nA key has no session, so it has no step-up: every operation marked as requiring one is refused to a key, whatever scopes it carries. Those are the irreversible ones — export, deletion, key rotation, changing who is in the workspace — and a person has to be present for them.\n\nWebhooks. A workspace registers an endpoint with `POST /v1/workspace/webhooks`, naming the events it wants, and lists its endpoints with `GET /v1/workspace/webhooks`, which also answers the event names a filter may use. Each delivery is a JSON POST to the endpoint, signed in `X-BatonDeck-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<body>\" under the endpoint's secret>` (two `v1` values while a rotated secret is still accepted), and carrying `X-BatonDeck-Event-Id`, `X-BatonDeck-Event-Type` and `X-BatonDeck-Delivery`. Nothing is delivered while the workspace's `webhooks_enabled` flag is off; the list says which it is, as `enabled`.",
    "x-hdtp-docs-source": {
      "repo": "batondeck",
      "commit": "17b851a892ec390e6150202347cdba0cc14ad8cc"
    }
  },
  "servers": [
    {
      "url": "https://api.batondeck.com"
    }
  ],
  "paths": {
    "/v1/workspace/pause": {
      "post": {
        "operationId": "pauseWorkspace",
        "summary": "Pause the workspace: restriction of processing, which you set and you lift",
        "description": "Requires the `batondeck:workspace:admin` permission (action `workspace:pause`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "Pause the workspace: restriction of processing, which you set and you lift",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "paused_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "paused_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "resumeWorkspace",
        "summary": "Lift your own pause",
        "description": "Requires the `batondeck:workspace:admin` permission (action `workspace:resume`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Lift your own pause",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "paused": {
                      "type": "boolean"
                    },
                    "suspended": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "paused",
                    "suspended"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/deletion": {
      "post": {
        "operationId": "requestWorkspaceDeletion",
        "summary": "Schedule this workspace for deletion, after a seven-day hold",
        "description": "Requires the `batondeck:workspace:admin` permission (action `workspace:delete`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "Schedule this workspace for deletion, after a seven-day hold",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hold_until": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "hold_until"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "cancelWorkspaceDeletion",
        "summary": "Change your mind inside the seven days",
        "description": "Requires the `batondeck:workspace:admin` permission (action `workspace:cancel-deletion`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Change your mind inside the seven days",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace": {
      "get": {
        "operationId": "getWorkspace",
        "summary": "The workspace this session is signed in to",
        "description": "Requires the `batondeck:workspace:read` permission (action `workspace:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The workspace this session is signed in to",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string"
                    },
                    "jurisdiction": {
                      "type": "string"
                    },
                    "hold_until": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "paused_at": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "session_max_age_hours": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "status",
                    "plan",
                    "jurisdiction",
                    "hold_until",
                    "paused_at",
                    "session_max_age_hours",
                    "created_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateWorkspace",
        "summary": "Rename the workspace",
        "description": "Requires the `batondeck:workspace:admin` permission (action `workspace:update`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Rename the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "plan": {
                      "type": "string"
                    },
                    "jurisdiction": {
                      "type": "string"
                    },
                    "hold_until": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "paused_at": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "session_max_age_hours": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "created_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "slug",
                    "status",
                    "plan",
                    "jurisdiction",
                    "hold_until",
                    "paused_at",
                    "session_max_age_hours",
                    "created_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "session_max_age_hours": {
                    "anyOf": [
                      {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 168
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workspace/plan": {
      "get": {
        "operationId": "getWorkspacePlan",
        "summary": "The plan in force and every entitlement it resolves to",
        "description": "Requires the `batondeck:billing:read` permission (action `plan:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The plan in force and every entitlement it resolves to",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ],
                      "additionalProperties": false
                    },
                    "entitlements": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "value": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "boolean"
                              },
                              {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            ]
                          },
                          "source": {
                            "type": "string"
                          },
                          "expires_at": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "key",
                          "value",
                          "source",
                          "expires_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "plan",
                    "entitlements"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/residency": {
      "get": {
        "operationId": "getResidency",
        "summary": "Where this workspace's data is held, and whether the Enterprise residency guarantee is in effect",
        "description": "Requires the `batondeck:workspace:read` permission (action `residency:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Where this workspace's data is held, and whether the Enterprise residency guarantee is in effect",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "jurisdiction": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "eu",
                            "us"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "entitled": {
                      "type": "boolean"
                    },
                    "in_effect": {
                      "type": "boolean"
                    },
                    "reason": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "covered": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "not_covered": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "jurisdiction",
                    "entitled",
                    "in_effect",
                    "reason",
                    "covered",
                    "not_covered"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/sso": {
      "get": {
        "operationId": "getSso",
        "summary": "Whether this workspace requires single sign-on, its connections and its domains",
        "description": "Requires the `batondeck:workspace:read` permission (action `sso:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Whether this workspace requires single sign-on, its connections and its domains",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entitled": {
                      "type": "boolean"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "provider_configured": {
                      "type": "boolean"
                    },
                    "connections": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "type",
                          "state"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "domains": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "domain": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "domain",
                          "state"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "signed_in_with_sso": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "entitled",
                    "required",
                    "provider_configured",
                    "connections",
                    "domains",
                    "signed_in_with_sso"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "setSsoRequired",
        "summary": "Require single sign-on for this workspace, or stop requiring it",
        "description": "Requires the `batondeck:workspace:admin` permission (action `sso:manage`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Require single sign-on for this workspace, or stop requiring it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "entitled": {
                      "type": "boolean"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "provider_configured": {
                      "type": "boolean"
                    },
                    "connections": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "type",
                          "state"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "domains": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "domain": {
                            "type": "string"
                          },
                          "state": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "domain",
                          "state"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "signed_in_with_sso": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "entitled",
                    "required",
                    "provider_configured",
                    "connections",
                    "domains",
                    "signed_in_with_sso"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "required": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "required"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workspace/sso/portal-link": {
      "post": {
        "operationId": "createSsoPortalLink",
        "summary": "A short-lived link into the identity provider's Admin Portal, to set up a connection or verify a domain",
        "description": "Requires the `batondeck:workspace:admin` permission (action `sso:manage`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "A short-lived link into the identity provider's Admin Portal, to set up a connection or verify a domain",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "url"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "intent": {
                    "type": "string",
                    "enum": [
                      "sso",
                      "domain_verification"
                    ]
                  }
                },
                "required": [
                  "intent"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workspace/billing": {
      "get": {
        "operationId": "getBilling",
        "summary": "Whether a plan can be bought here, which ones, and whether a subscription exists to manage",
        "description": "Requires the `batondeck:billing:read` permission (action `plan:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Whether a plan can be bought here, which ones, and whether a subscription exists to manage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "purchasable": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "managed": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "enabled",
                    "purchasable",
                    "managed"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/billing/checkout": {
      "post": {
        "operationId": "openCheckout",
        "summary": "A Stripe Checkout link for a paid plan (review P-14)",
        "description": "Requires the `batondeck:billing:manage` permission (action `plan:change`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "A Stripe Checkout link for a paid plan (review P-14)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "url"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "pro",
                      "team",
                      "enterprise"
                    ]
                  }
                },
                "required": [
                  "plan"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workspace/billing/portal": {
      "post": {
        "operationId": "openBillingPortal",
        "summary": "A link to Stripe's customer portal, where the subscription is changed or cancelled",
        "description": "Requires the `batondeck:billing:manage` permission (action `plan:change`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "A link to Stripe's customer portal, where the subscription is changed or cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "url"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workspace/members": {
      "get": {
        "operationId": "listMembers",
        "summary": "The owners in the workspace and which of them administer it",
        "description": "Requires the `batondeck:members:read` permission (action `member:list`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The owners in the workspace and which of them administer it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "owner_id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "workspace_admin": {
                            "type": "boolean"
                          },
                          "role": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "owner_id",
                          "name",
                          "workspace_admin",
                          "role",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "members"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/usage": {
      "get": {
        "operationId": "listUsage",
        "summary": "Rolled-up usage, newest day first, one row per day and metric",
        "description": "Requires the `batondeck:billing:read` permission (action `plan:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": true,
            "schema": {
              "default": 200,
              "type": "integer",
              "minimum": 1,
              "maximum": 1000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rolled-up usage, newest day first, one row per day and metric",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "usage": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "day": {
                            "type": "string"
                          },
                          "metric": {
                            "type": "string"
                          },
                          "value": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "day",
                          "metric",
                          "value"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "usage"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/audit": {
      "get": {
        "operationId": "listWorkspaceAudit",
        "summary": "The newest rows of the workspace's audit chain, oldest first within the window",
        "description": "Requires the `batondeck:audit:read` permission (action `audit:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": true,
            "schema": {
              "default": 100,
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The newest rows of the workspace's audit chain, oldest first within the window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "seq": {
                            "type": "number"
                          },
                          "ts": {
                            "type": "number"
                          },
                          "actor": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "details": {
                            "type": "string"
                          },
                          "prev_hash": {
                            "type": "string"
                          },
                          "hash": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "seq",
                          "ts",
                          "actor",
                          "kind",
                          "details",
                          "prev_hash",
                          "hash"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "seq": {
                            "type": "number"
                          },
                          "ts": {
                            "type": "number"
                          },
                          "actor": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "details": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "seq",
                          "ts",
                          "actor",
                          "kind",
                          "details"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "names": {
                      "type": "object",
                      "properties": {
                        "owners": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "string"
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "identities": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "string"
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "keys": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "object",
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "revoked": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "name",
                                  "revoked"
                                ],
                                "additionalProperties": false
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "grants": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "object",
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "revoked": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "name",
                                  "revoked"
                                ],
                                "additionalProperties": false
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "owners",
                        "identities",
                        "keys",
                        "grants"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "rows",
                    "events",
                    "names"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/settings": {
      "get": {
        "operationId": "listSettings",
        "summary": "The five knobs, each fixed by the platform, with the reason",
        "description": "Requires the `batondeck:workspace:read` permission (action `workspace:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The five knobs, each fixed by the platform, with the reason",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "settings": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          },
                          "locked": {
                            "type": "boolean"
                          },
                          "reason": {
                            "type": "string"
                          },
                          "restart_scoped": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "key",
                          "value",
                          "locked",
                          "reason",
                          "restart_scoped"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "settings"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions": {
      "get": {
        "operationId": "listSessions",
        "summary": "The caller's own signed-in sessions, across every workspace, newest first; the one this request came in on is marked",
        "description": "Requires the `batondeck:workspace:read` permission (action `session:list`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The caller's own signed-in sessions, across every workspace, newest first; the one this request came in on is marked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "device": {
                            "type": "string"
                          },
                          "country": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "number"
                          },
                          "last_seen_at": {
                            "type": "number"
                          },
                          "expires_at": {
                            "type": "number"
                          },
                          "current": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "id",
                          "device",
                          "country",
                          "created_at",
                          "last_seen_at",
                          "expires_at",
                          "current"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "sessions"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/{id}": {
      "delete": {
        "operationId": "revokeSession",
        "summary": "Sign one of the caller's other sessions out; it is refused on its next request",
        "description": "Requires the `batondeck:workspace:read` permission (action `session:revoke`).\n\nAllowed while the workspace is paused, suspended or on deletion hold: it only takes access away.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No body."
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/sessions/sign-out-others": {
      "post": {
        "operationId": "revokeOtherSessions",
        "summary": "Sign out every session of the caller's but the one this request came in on",
        "description": "Requires the `batondeck:workspace:read` permission (action `session:revoke`).\n\nAllowed while the workspace is paused, suspended or on deletion hold: it only takes access away.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Sign out every session of the caller's but the one this request came in on",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "revoked"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/factors": {
      "get": {
        "operationId": "listFactors",
        "summary": "The caller's own second factors: whether two-factor authentication is on, and each authenticator",
        "description": "Requires the `batondeck:workspace:read` permission (action `factor:list`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The caller's own second factors: whether two-factor authentication is on, and each authenticator",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "factors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "type": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "type",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "enabled",
                    "factors"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "startFactorEnrolment",
        "summary": "Start adding an authenticator app: the QR code and secret, shown once, and the enrolment its first code finishes. Nothing changes until then",
        "description": "Requires the `batondeck:workspace:read` permission (action `factor:enroll`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "Start adding an authenticator app: the QR code and secret, shown once, and the enrolment its first code finishes. Nothing changes until then",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enrolment": {
                      "type": "string"
                    },
                    "secret": {
                      "type": "string"
                    },
                    "uri": {
                      "type": "string"
                    },
                    "qr_code": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "enrolment",
                    "secret",
                    "uri",
                    "qr_code",
                    "expires_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/factors/verify": {
      "post": {
        "operationId": "verifyFactorEnrolment",
        "summary": "Finish adding an authenticator with the code it shows: from then on, signing in asks for it",
        "description": "Requires the `batondeck:workspace:read` permission (action `factor:enroll`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "Finish adding an authenticator with the code it shows: from then on, signing in asks for it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "factor": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string"
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "type",
                        "created_at"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "factor"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enrolment": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2048
                  },
                  "code": {
                    "type": "string",
                    "pattern": "^\\d{6}$"
                  }
                },
                "required": [
                  "enrolment",
                  "code"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/factors/{id}": {
      "delete": {
        "operationId": "removeFactor",
        "summary": "Remove one of the caller's authenticators; with none left, signing in asks for no code",
        "description": "Requires the `batondeck:workspace:read` permission (action `factor:remove`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No body."
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "Agent keys: the caller's own, or every owner's for an admin. Never the keys themselves",
        "description": "Requires the `batondeck:workspace:read` permission (action `apikey:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Agent keys: the caller's own, or every owner's for an admin. Never the keys themselves",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "prefix": {
                            "type": "string"
                          },
                          "owner_id": {
                            "type": "string"
                          },
                          "scopes": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "identity_ids": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "created_at": {
                            "type": "number"
                          },
                          "last_used_at": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "revoked_at": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "expires_at": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "made_by": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "prefix",
                          "owner_id",
                          "scopes",
                          "identity_ids",
                          "created_at",
                          "last_used_at",
                          "revoked_at",
                          "expires_at",
                          "made_by"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "limit": {
                      "type": "number"
                    },
                    "live": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "keys",
                    "limit",
                    "live"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "mintApiKey",
        "summary": "Mint an agent key for the caller. The key is in this response and nowhere else, ever",
        "description": "Requires the `batondeck:workspace:read` permission (action `apikey:mint`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "Mint an agent key for the caller. The key is in this response and nowhere else, ever",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "api_key": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "prefix": {
                          "type": "string"
                        },
                        "owner_id": {
                          "type": "string"
                        },
                        "scopes": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "identity_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "created_at": {
                          "type": "number"
                        },
                        "last_used_at": {
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "revoked_at": {
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "expires_at": {
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "made_by": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "prefix",
                        "owner_id",
                        "scopes",
                        "identity_ids",
                        "created_at",
                        "last_used_at",
                        "revoked_at",
                        "expires_at",
                        "made_by"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "key",
                    "api_key"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "scopes": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "everything": {
                    "type": "boolean"
                  },
                  "identity_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "expires_in_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 365
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workspace/keys/{id}": {
      "delete": {
        "operationId": "revokeApiKey",
        "summary": "Revoke an agent key: the caller's own, or any for an admin. The row stays, so the audit rows it wrote still name it",
        "description": "Requires the `batondeck:workspace:read` permission (action `apikey:revoke`).\n\nAllowed while the workspace is paused, suspended or on deletion hold: it only takes access away.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No body."
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "The workspace's webhook endpoints. Never their signing secrets",
        "description": "Requires the `batondeck:workspace:admin` permission (action `webhook:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The workspace's webhook endpoints. Never their signing secrets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "endpoints": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "events": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "status": {
                            "type": "string"
                          },
                          "consecutive_failures": {
                            "type": "number"
                          },
                          "rotating_until": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "created_at": {
                            "type": "number"
                          },
                          "updated_at": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "id",
                          "url",
                          "events",
                          "status",
                          "consecutive_failures",
                          "rotating_until",
                          "created_at",
                          "updated_at"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "enabled": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "endpoints",
                    "events",
                    "enabled"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Register an endpoint. The signing secret is in this answer and nowhere else",
        "description": "Requires the `batondeck:workspace:admin` permission (action `webhook:create`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "Register an endpoint. The signing secret is in this answer and nowhere else",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "endpoint": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string"
                        },
                        "events": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "status": {
                          "type": "string"
                        },
                        "consecutive_failures": {
                          "type": "number"
                        },
                        "rotating_until": {
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "created_at": {
                          "type": "number"
                        },
                        "updated_at": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "id",
                        "url",
                        "events",
                        "status",
                        "consecutive_failures",
                        "rotating_until",
                        "created_at",
                        "updated_at"
                      ],
                      "additionalProperties": false
                    },
                    "secret": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "endpoint",
                    "secret"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "minLength": 1
                  },
                  "events": {
                    "default": [],
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "url",
                  "events"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workspace/webhooks/{id}/secret": {
      "post": {
        "operationId": "rotateWebhookSecret",
        "summary": "A new signing secret; the old one keeps working for 24 hours",
        "description": "Requires the `batondeck:workspace:admin` permission (action `webhook:manage`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A new signing secret; the old one keeps working for 24 hours",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "secret": {
                      "type": "string"
                    },
                    "previous_accepted_until": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "secret",
                    "previous_accepted_until"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/webhooks/{id}": {
      "patch": {
        "operationId": "setWebhookStatus",
        "summary": "Pause an endpoint, or resume one — resuming clears its failure run",
        "description": "Requires the `batondeck:workspace:admin` permission (action `webhook:manage`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pause an endpoint, or resume one — resuming clears its failure run",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "endpoints": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "events": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "status": {
                            "type": "string"
                          },
                          "consecutive_failures": {
                            "type": "number"
                          },
                          "rotating_until": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "created_at": {
                            "type": "number"
                          },
                          "updated_at": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "id",
                          "url",
                          "events",
                          "status",
                          "consecutive_failures",
                          "rotating_until",
                          "created_at",
                          "updated_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "endpoints"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ]
                  }
                },
                "required": [
                  "status"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Remove an endpoint. Its delivery log stays, so what it did is still readable",
        "description": "Requires the `batondeck:workspace:admin` permission (action `webhook:delete`).\n\nAllowed while the workspace is paused, suspended or on deletion hold: it only takes access away.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No body."
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "What was attempted, when, and what came back",
        "description": "Requires the `batondeck:workspace:admin` permission (action `webhook:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": true,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What was attempted, when, and what came back",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deliveries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "endpoint_id": {
                            "type": "string"
                          },
                          "event_id": {
                            "type": "string"
                          },
                          "event_type": {
                            "type": "string"
                          },
                          "attempt": {
                            "type": "number"
                          },
                          "status": {
                            "type": "string"
                          },
                          "http_status": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "latency_ms": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "response_excerpt": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "next_attempt_at": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "created_at": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "id",
                          "endpoint_id",
                          "event_id",
                          "event_type",
                          "attempt",
                          "status",
                          "http_status",
                          "latency_ms",
                          "response_excerpt",
                          "next_attempt_at",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "deliveries"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/domains": {
      "get": {
        "operationId": "listDomains",
        "summary": "Every hostname the workspace holds, the platform name included",
        "description": "Requires the `batondeck:workspace:read` permission (action `workspace:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "Every hostname the workspace holds, the platform name included",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domains": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "hostname": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "custom_domain": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "number"
                          },
                          "updated_at": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "hostname",
                          "kind",
                          "status",
                          "custom_domain",
                          "created_at",
                          "updated_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "domains"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "addCustomDomain",
        "summary": "Add a custom domain, on a plan that carries `custom_domain`; answers the DNS records to create",
        "description": "Requires the `batondeck:workspace:admin` permission (action `domain:manage`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "201": {
            "description": "Add a custom domain, on a plan that carries `custom_domain`; answers the DNS records to create",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hostname": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "reason": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "CNAME",
                              "TXT"
                            ]
                          },
                          "name": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "type",
                          "name",
                          "value"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "identities": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "created_at": {
                      "type": "number"
                    },
                    "updated_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "hostname",
                    "status",
                    "reason",
                    "records",
                    "identities",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hostname": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 253
                  }
                },
                "required": [
                  "hostname"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/workspace/domains/{hostname}": {
      "get": {
        "operationId": "getCustomDomain",
        "summary": "One custom domain: its status, the DNS records to create and what is still needed, read live",
        "description": "Requires the `batondeck:workspace:read` permission (action `workspace:read`).",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One custom domain: its status, the DNS records to create and what is still needed, read live",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hostname": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "reason": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "CNAME",
                              "TXT"
                            ]
                          },
                          "name": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "type",
                          "name",
                          "value"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "identities": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "created_at": {
                      "type": "number"
                    },
                    "updated_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "hostname",
                    "status",
                    "reason",
                    "records",
                    "identities",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeCustomDomain",
        "summary": "Remove a custom domain no identity lives at; its certificate stops being served",
        "description": "Requires the `batondeck:workspace:admin` permission (action `domain:remove`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No body."
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/domains/{hostname}/check": {
      "post": {
        "operationId": "checkCustomDomain",
        "summary": "Check the domain now instead of waiting for the five-minute poll, and record the result",
        "description": "Requires the `batondeck:workspace:admin` permission (action `domain:manage`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "hostname",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Check the domain now instead of waiting for the five-minute poll, and record the result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hostname": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "reason": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "CNAME",
                              "TXT"
                            ]
                          },
                          "name": {
                            "type": "string"
                          },
                          "value": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "type",
                          "name",
                          "value"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "identities": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "created_at": {
                      "type": "number"
                    },
                    "updated_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "hostname",
                    "status",
                    "reason",
                    "records",
                    "identities",
                    "created_at",
                    "updated_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/export": {
      "get": {
        "operationId": "exportIdentity",
        "summary": "This identity's contacts, threads, messages and files as one unencrypted zip (design §4), streamed and never stored",
        "description": "Requires the `batondeck:workspace:admin` permission (action `identity:export`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "acknowledge",
            "in": "query",
            "required": false,
            "schema": {
              "description": "Must be \"unencrypted\": This file is not encrypted. Anyone who gets it can read your contact list and all your conversations and files. It holds no keys, so it cannot be used to speak as you. Keep it where you keep private documents, and delete it once it has been imported.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "This identity's contacts, threads, messages and files as one unencrypted zip (design §4), streamed and never stored",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/export/report": {
      "get": {
        "operationId": "getExportReport",
        "summary": "What this identity's export would say of itself, without the file: each import ceiling it passes, and every message it leaves out, by id and why",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What this identity's export would say of itself, without the file: each import ceiling it passes, and every message it leaves out, by id and why",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "warnings": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "left_out": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "reason"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "bytes": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "warnings",
                    "left_out",
                    "bytes"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/workspace/export": {
      "get": {
        "operationId": "exportWorkspace",
        "summary": "Everything this workspace holds, as one unencrypted zip streamed and never stored: the control-plane document, every identity's export zip, and an index",
        "description": "Requires the `batondeck:workspace:admin` permission (action `export:read`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.",
        "tags": [
          "workspace"
        ],
        "parameters": [
          {
            "name": "acknowledge",
            "in": "query",
            "required": false,
            "schema": {
              "description": "Must be \"unencrypted\": This file is not encrypted. Anyone who gets it can read your contact list and all your conversations and files. It holds no keys, so it cannot be used to speak as you. Keep it where you keep private documents, and delete it once it has been imported.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Everything this workspace holds, as one unencrypted zip streamed and never stored: the control-plane document, every identity's export zip, and an index",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/presets": {
      "get": {
        "operationId": "listPresets",
        "summary": "The permission presets a contact or an invite can be put on",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:list`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The permission presets a contact or an invite can be put on",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "presets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "permissions": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          }
                        },
                        "required": [
                          "name",
                          "permissions"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "permissions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "presets",
                    "permissions"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/presets": {
      "get": {
        "operationId": "listIdentityPresets",
        "summary": "This identity's own preset bundles, and which have been edited",
        "description": "Requires the `batondeck:contacts:read` permission (action `contact:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "This identity's own preset bundles, and which have been edited",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "presets": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "permissions": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "edited": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "name",
                          "permissions",
                          "edited"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "presets"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/presets/{name}": {
      "patch": {
        "operationId": "setIdentityPreset",
        "summary": "Change what a preset grants, for this identity",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Change what a preset grants, for this identity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "permissions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "name",
                    "permissions"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "permissions": {
                    "maxItems": 64,
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "permissions"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities": {
      "get": {
        "operationId": "listIdentities",
        "summary": "The identities in the signed-in workspace",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:list`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The identities in the signed-in workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "identities": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "account_id": {
                            "type": "string"
                          },
                          "fingerprint": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "hostname": {
                            "type": "string"
                          },
                          "certified": {
                            "type": "boolean"
                          },
                          "created_at": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "slug",
                          "account_id",
                          "fingerprint",
                          "status",
                          "hostname",
                          "certified",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "identities"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}": {
      "get": {
        "operationId": "getIdentity",
        "summary": "One identity: its key fingerprint, hostname and card",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One identity: its key fingerprint, hostname and card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "slug": {
                      "type": "string"
                    },
                    "account_id": {
                      "type": "string"
                    },
                    "fingerprint": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "hostname": {
                      "type": "string"
                    },
                    "certified": {
                      "type": "boolean"
                    },
                    "created_at": {
                      "type": "number"
                    },
                    "card": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "slug",
                    "account_id",
                    "fingerprint",
                    "status",
                    "hostname",
                    "certified",
                    "created_at",
                    "card"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteIdentity",
        "summary": "Erase one identity: its object, its media, and its routing row",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:delete`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No body."
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/card": {
      "get": {
        "operationId": "getIdentityCard",
        "summary": "The identity's own contact card, as a peer would fetch it",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The identity's own contact card, as a peer would fetch it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "card": {
                      "type": "string"
                    },
                    "root_fingerprint": {
                      "type": "string"
                    },
                    "kid": {
                      "type": "string"
                    },
                    "endpoint": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "card",
                    "root_fingerprint",
                    "kid",
                    "endpoint"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/grants": {
      "get": {
        "operationId": "listIdentityGrants",
        "summary": "Which owners may act as this identity. Several is a shared inbox (SPEC §3.3)",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Which owners may act as this identity. Several is a shared inbox (SPEC §3.3)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "grants": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "owner_id": {
                            "type": "string"
                          },
                          "role": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "owner_id",
                          "role",
                          "created_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "grants"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "grantIdentity",
        "summary": "Let another owner act as this identity, which is how a shared inbox is made",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:grant`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Let another owner act as this identity, which is how a shared inbox is made",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "owner_id": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "owner_id",
                    "role",
                    "created_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "owner_id": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "owner_id"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/grants/{ownerId}": {
      "delete": {
        "operationId": "revokeIdentityGrant",
        "summary": "Take back an owner's access to this identity",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:revoke`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nAllowed while the workspace is paused, suspended or on deletion hold: it only takes access away.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "ownerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Take back an owner's access to this identity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "revoked"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/address": {
      "post": {
        "operationId": "moveIdentityAddress",
        "summary": "Switch the identity to the address its current leaf names: the routing half of a move (SPEC §5.3, §9)",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:move`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Switch the identity to the address its current leaf names: the routing half of a move (SPEC §5.3, §9)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "address": {
                      "type": "string"
                    },
                    "from": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "vacated_until": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "address",
                    "from",
                    "vacated_until"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "address": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 300
                  }
                },
                "required": [
                  "address"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/import/review": {
      "post": {
        "operationId": "reviewImportArchive",
        "summary": "Review an export zip sent as base64url in JSON (up to 6,000,000 bytes): validated whole and its contacts shown, nothing written; the import names the digest this answers",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:import`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Review an export zip sent as base64url in JSON (up to 6,000,000 bytes): validated whole and its contacts shown, nothing written; the import names the digest this answers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "digest": {
                      "type": "string"
                    },
                    "root_fingerprint": {
                      "type": "string"
                    },
                    "contacts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "root": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "display_name": {
                            "type": "string"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "leaf": {
                            "type": "boolean"
                          },
                          "action": {
                            "type": "string",
                            "enum": [
                              "add",
                              "pin",
                              "keep",
                              "skip"
                            ]
                          },
                          "conflicts": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "field": {
                                  "type": "string"
                                },
                                "held": {
                                  "anyOf": [
                                    {
                                      "type": "string"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ]
                                },
                                "row": {
                                  "anyOf": [
                                    {
                                      "type": "string"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ]
                                }
                              },
                              "required": [
                                "field",
                                "held",
                                "row"
                              ],
                              "additionalProperties": false
                            }
                          }
                        },
                        "required": [
                          "root",
                          "name",
                          "display_name",
                          "endpoint",
                          "status",
                          "leaf",
                          "action",
                          "conflicts"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "counts": {
                      "type": "object",
                      "properties": {
                        "contacts": {
                          "type": "number"
                        },
                        "threads": {
                          "type": "number"
                        },
                        "messages": {
                          "type": "number"
                        },
                        "media": {
                          "type": "number"
                        },
                        "media_bytes": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "contacts",
                        "threads",
                        "messages",
                        "media",
                        "media_bytes"
                      ],
                      "additionalProperties": false
                    },
                    "expires_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "digest",
                    "root_fingerprint",
                    "contacts",
                    "counts",
                    "expires_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "archive": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "archive"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      },
      "put": {
        "operationId": "uploadImportReview",
        "summary": "Review an export zip sent as the raw body (application/zip, up to the host's import ceiling): the same review as the JSON door",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:import`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Review an export zip sent as the raw body (application/zip, up to the host's import ceiling): the same review as the JSON door",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "digest": {
                      "type": "string"
                    },
                    "root_fingerprint": {
                      "type": "string"
                    },
                    "contacts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "root": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "display_name": {
                            "type": "string"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "leaf": {
                            "type": "boolean"
                          },
                          "action": {
                            "type": "string",
                            "enum": [
                              "add",
                              "pin",
                              "keep",
                              "skip"
                            ]
                          },
                          "conflicts": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "field": {
                                  "type": "string"
                                },
                                "held": {
                                  "anyOf": [
                                    {
                                      "type": "string"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ]
                                },
                                "row": {
                                  "anyOf": [
                                    {
                                      "type": "string"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ]
                                }
                              },
                              "required": [
                                "field",
                                "held",
                                "row"
                              ],
                              "additionalProperties": false
                            }
                          }
                        },
                        "required": [
                          "root",
                          "name",
                          "display_name",
                          "endpoint",
                          "status",
                          "leaf",
                          "action",
                          "conflicts"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "counts": {
                      "type": "object",
                      "properties": {
                        "contacts": {
                          "type": "number"
                        },
                        "threads": {
                          "type": "number"
                        },
                        "messages": {
                          "type": "number"
                        },
                        "media": {
                          "type": "number"
                        },
                        "media_bytes": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "contacts",
                        "threads",
                        "messages",
                        "media",
                        "media_bytes"
                      ],
                      "additionalProperties": false
                    },
                    "expires_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "digest",
                    "root_fingerprint",
                    "contacts",
                    "counts",
                    "expires_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "description": "The file itself, with its Content-Length, up to 99614720 bytes.",
          "content": {
            "application/zip": {
              "schema": {
                "type": "string",
                "format": "binary",
                "maxLength": 99614720
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/import/review/{digest}": {
      "get": {
        "operationId": "getImportReview",
        "summary": "An open review of an import: the contacts shown for that file, until it closes",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "digest",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "An open review of an import: the contacts shown for that file, until it closes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "digest": {
                      "type": "string"
                    },
                    "root_fingerprint": {
                      "type": "string"
                    },
                    "contacts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "root": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "display_name": {
                            "type": "string"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "leaf": {
                            "type": "boolean"
                          },
                          "action": {
                            "type": "string",
                            "enum": [
                              "add",
                              "pin",
                              "keep",
                              "skip"
                            ]
                          },
                          "conflicts": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "field": {
                                  "type": "string"
                                },
                                "held": {
                                  "anyOf": [
                                    {
                                      "type": "string"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ]
                                },
                                "row": {
                                  "anyOf": [
                                    {
                                      "type": "string"
                                    },
                                    {
                                      "type": "null"
                                    }
                                  ]
                                }
                              },
                              "required": [
                                "field",
                                "held",
                                "row"
                              ],
                              "additionalProperties": false
                            }
                          }
                        },
                        "required": [
                          "root",
                          "name",
                          "display_name",
                          "endpoint",
                          "status",
                          "leaf",
                          "action",
                          "conflicts"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "counts": {
                      "type": "object",
                      "properties": {
                        "contacts": {
                          "type": "number"
                        },
                        "threads": {
                          "type": "number"
                        },
                        "messages": {
                          "type": "number"
                        },
                        "media": {
                          "type": "number"
                        },
                        "media_bytes": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "contacts",
                        "threads",
                        "messages",
                        "media",
                        "media_bytes"
                      ],
                      "additionalProperties": false
                    },
                    "expires_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "digest",
                    "root_fingerprint",
                    "contacts",
                    "counts",
                    "expires_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "cancelImportReview",
        "summary": "Close an open review of an import before its time: the review and the uploaded file it holds go, and nothing else changes",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:import`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "digest",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No body."
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/import": {
      "post": {
        "operationId": "importArchive",
        "summary": "Take in a reviewed export zip, named by its review's digest: the file the review holds is read again and written, refused unless it reads as reviewed; answers the renew request the wallet signs next",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:import`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Take in a reviewed export zip, named by its review's digest: the file the review holds is read again and written, refused unless it reads as reviewed; answers the renew request the wallet signs next",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "root_fingerprint": {
                      "type": "string"
                    },
                    "contacts": {
                      "type": "object",
                      "properties": {
                        "written": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "kept": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "leafless": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "conflicts": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "written",
                        "kept",
                        "leafless",
                        "conflicts"
                      ],
                      "additionalProperties": false
                    },
                    "threads": {
                      "type": "number"
                    },
                    "messages": {
                      "type": "number"
                    },
                    "media": {
                      "type": "number"
                    },
                    "renewal": {
                      "type": "object",
                      "properties": {
                        "csr": {
                          "type": "string"
                        },
                        "endpoint": {
                          "type": "string"
                        },
                        "purpose": {
                          "type": "string"
                        },
                        "suggested_not_after": {
                          "type": "string"
                        },
                        "previous_not_before": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "key_fingerprint": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "csr",
                        "endpoint",
                        "purpose",
                        "suggested_not_after",
                        "previous_not_before",
                        "key_fingerprint"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "root_fingerprint",
                    "contacts",
                    "threads",
                    "messages",
                    "media",
                    "renewal"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "digest": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$"
                  }
                },
                "required": [
                  "digest"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/statuses": {
      "get": {
        "operationId": "listStatusChoices",
        "summary": "The choices an identity's status may be set to, in the order to offer them, and how recent a use keeps Auto available",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:list`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The choices an identity's status may be set to, in the order to offer them, and how recent a use keeps Auto available",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "statuses": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "auto",
                          "available",
                          "not_available",
                          "disabled"
                        ]
                      }
                    },
                    "auto_window_ms": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "statuses",
                    "auto_window_ms"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/settings": {
      "get": {
        "operationId": "getIdentitySettings",
        "summary": "This identity's own settings: accept_new_hosts, the owner's status and moved_away_at",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "This identity's own settings: accept_new_hosts, the owner's status and moved_away_at",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accept_new_hosts": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "auto",
                        "available",
                        "not_available",
                        "disabled"
                      ]
                    },
                    "status_updated_at": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "moved_away_at": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "accept_new_hosts",
                    "status",
                    "status_updated_at",
                    "moved_away_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateIdentitySettings",
        "summary": "What happens when a pinned contact turns up at a new address (accept_new_hosts), the owner's status, which decides what contacts read with get_status, or, alone, moved_away: no renewal reminders",
        "description": "Requires the `batondeck:identities:manage` permission (action `identity:update`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What happens when a pinned contact turns up at a new address (accept_new_hosts), the owner's status, which decides what contacts read with get_status, or, alone, moved_away: no renewal reminders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accept_new_hosts": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "auto",
                        "available",
                        "not_available",
                        "disabled"
                      ]
                    },
                    "status_updated_at": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "moved_away_at": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "accept_new_hosts",
                    "status",
                    "status_updated_at",
                    "moved_away_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "accept_new_hosts": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "ask"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "available",
                      "not_available",
                      "disabled"
                    ]
                  },
                  "moved_away": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/addresses/pending": {
      "get": {
        "operationId": "listPendingAddresses",
        "summary": "Contacts waiting at a new address for the owner's decision (SPEC §5.3)",
        "description": "Requires the `batondeck:contacts:read` permission (action `contact:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacts waiting at a new address for the owner's decision (SPEC §5.3)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pending": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "root": {
                            "type": "string"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "current_endpoint": {
                            "type": "string"
                          },
                          "why": {
                            "type": "string"
                          },
                          "at": {
                            "type": "number"
                          },
                          "display_name": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "root",
                          "endpoint",
                          "current_endpoint",
                          "why",
                          "at",
                          "display_name"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "pending"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/addresses/{root}/approve": {
      "post": {
        "operationId": "approvePendingAddress",
        "summary": "Re-pin the contact at the new address, as accept_new_hosts: auto would have",
        "description": "Requires the `batondeck:contacts:manage` permission (action `address:decide`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "root",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Re-pin the contact at the new address, as accept_new_hosts: auto would have",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "root": {
                      "type": "string"
                    },
                    "endpoint": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "root",
                    "endpoint"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/addresses/{root}/reject": {
      "post": {
        "operationId": "rejectPendingAddress",
        "summary": "Leave the pin where it is; the new address is a stranger the owner may block",
        "description": "Requires the `batondeck:contacts:manage` permission (action `address:decide`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "root",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Leave the pin where it is; the new address is a stranger the owner may block",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "root": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "root"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/storage": {
      "get": {
        "operationId": "getStorage",
        "summary": "Bytes this identity holds, and the ceiling it is held against",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bytes this identity holds, and the ceiling it is held against",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "used": {
                      "type": "number"
                    },
                    "quota": {
                      "type": "number"
                    },
                    "blobs": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "used",
                    "quota",
                    "blobs"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/audit": {
      "get": {
        "operationId": "listIdentityAudit",
        "summary": "The identity's own audit chain, newest first",
        "description": "Requires the `batondeck:audit:read` permission (action `audit:identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": true,
            "schema": {
              "default": 100,
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "subject",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          },
          {
            "name": "action_like",
            "in": "query",
            "required": false,
            "schema": {
              "description": "only actions containing this",
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The identity's own audit chain, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "seq": {
                            "type": "number"
                          },
                          "ts": {
                            "type": "number"
                          },
                          "action": {
                            "type": "string"
                          },
                          "actor_kind": {
                            "type": "string"
                          },
                          "actor_id": {
                            "type": "string"
                          },
                          "resource": {
                            "type": "string"
                          },
                          "outcome": {
                            "type": "string"
                          },
                          "details": {
                            "type": "string"
                          },
                          "account_id": {
                            "type": "string"
                          },
                          "request_id": {
                            "type": "string"
                          },
                          "prev_hash": {
                            "type": "string"
                          },
                          "hash": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "seq",
                          "ts",
                          "action",
                          "actor_kind",
                          "actor_id",
                          "resource",
                          "outcome",
                          "details",
                          "account_id",
                          "request_id",
                          "prev_hash",
                          "hash"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "names": {
                      "type": "object",
                      "properties": {
                        "owners": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "string"
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "identities": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "string"
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "keys": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "object",
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "revoked": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "name",
                                  "revoked"
                                ],
                                "additionalProperties": false
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "grants": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "object",
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "revoked": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "name",
                                  "revoked"
                                ],
                                "additionalProperties": false
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "contacts": {
                          "anyOf": [
                            {
                              "type": "object",
                              "propertyNames": {
                                "type": "string"
                              },
                              "additionalProperties": {
                                "type": "string"
                              }
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "owners",
                        "identities",
                        "keys",
                        "grants",
                        "contacts"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "rows",
                    "names"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/certificate": {
      "get": {
        "operationId": "getCertificate",
        "summary": "Whether the identity is certified, and its root, chain, validity and pending CSR (SPEC §2)",
        "description": "Requires the `batondeck:identities:read` permission (action `cert:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Whether the identity is certified, and its root, chain, validity and pending CSR (SPEC §2)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "certified": {
                      "type": "boolean"
                    },
                    "root_fingerprint": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "chain": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "kid": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "endpoint": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "not_before": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "not_after": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "renewal_due": {
                      "type": "boolean"
                    },
                    "expired": {
                      "type": "boolean"
                    },
                    "pending_csr": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "endpoint": {
                              "type": "string"
                            },
                            "purpose": {
                              "type": "string"
                            },
                            "created_at": {
                              "type": "number"
                            },
                            "key_fingerprint": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "endpoint",
                            "purpose",
                            "created_at",
                            "key_fingerprint"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "superseded_kids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "accept_new_hosts": {
                      "type": "string"
                    },
                    "backup_verified_at": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "certified",
                    "root_fingerprint",
                    "chain",
                    "kid",
                    "endpoint",
                    "not_before",
                    "not_after",
                    "renewal_due",
                    "expired",
                    "pending_csr",
                    "superseded_kids",
                    "accept_new_hosts",
                    "backup_verified_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/csr": {
      "post": {
        "operationId": "issueCsr",
        "summary": "Mint a key and a certificate signing request for the wallet to sign (SPEC §9)",
        "description": "Requires the `batondeck:identities:manage` permission (action `cert:csr`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Mint a key and a certificate signing request for the wallet to sign (SPEC §9)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "csr": {
                      "type": "string"
                    },
                    "endpoint": {
                      "type": "string"
                    },
                    "purpose": {
                      "type": "string"
                    },
                    "suggested_not_after": {
                      "type": "string"
                    },
                    "previous_not_before": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "key_fingerprint": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "csr",
                    "endpoint",
                    "purpose",
                    "suggested_not_after",
                    "previous_not_before",
                    "key_fingerprint"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "purpose": {
                    "type": "string",
                    "enum": [
                      "signup",
                      "renew",
                      "move"
                    ]
                  },
                  "endpoint": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                },
                "required": [
                  "purpose"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/leaf": {
      "post": {
        "operationId": "installLeaf",
        "summary": "Install the chain the wallet issued: the identity becomes, or renews as, 2.0",
        "description": "Requires the `batondeck:identities:manage` permission (action `cert:install`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Install the chain the wallet issued: the identity becomes, or renews as, 2.0",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "root_fingerprint": {
                      "type": "string"
                    },
                    "kid": {
                      "type": "string"
                    },
                    "endpoint": {
                      "type": "string"
                    },
                    "not_before": {
                      "type": "string"
                    },
                    "not_after": {
                      "type": "string"
                    },
                    "purpose": {
                      "type": "string"
                    },
                    "first": {
                      "type": "boolean"
                    },
                    "superseded_kid": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "root_fingerprint",
                    "kid",
                    "endpoint",
                    "not_before",
                    "not_after",
                    "purpose",
                    "first",
                    "superseded_kid"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "chain": {
                    "minItems": 2,
                    "maxItems": 2,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "credential_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 512
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "backup_verified": {
                    "anyOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "chain"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/wallet-request": {
      "post": {
        "operationId": "openWalletRequest",
        "summary": "Mint a CSR and open a single-use request for the wallet page to sign (onboarding/wallet design §1a)",
        "description": "Requires the `batondeck:identities:manage` permission (action `cert:csr`).\n\nRequires a step-up: MFA enrolled and re-authenticated within 15 minutes. **Not reachable with an API key** — a key has no session and so can never step up.\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Mint a CSR and open a single-use request for the wallet page to sign (onboarding/wallet design §1a)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "endpoint": {
                      "type": "string"
                    },
                    "purpose": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "code",
                    "url",
                    "endpoint",
                    "purpose",
                    "expires_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "purpose": {
                    "type": "string",
                    "enum": [
                      "signup",
                      "renew",
                      "move"
                    ]
                  },
                  "endpoint": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512
                  }
                },
                "required": [
                  "purpose"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/wallet-request/{code}": {
      "get": {
        "operationId": "readWalletRequest",
        "summary": "What the wallet did with a request: still open, the chain it issued, or why it did not",
        "description": "Requires the `batondeck:identities:read` permission (action `cert:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What the wallet did with a request: still open, the chain it issued, or why it did not",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "state": {
                      "type": "string",
                      "enum": [
                        "open",
                        "answered",
                        "cancelled",
                        "failed",
                        "expired"
                      ]
                    },
                    "chain": {
                      "anyOf": [
                        {
                          "minItems": 2,
                          "maxItems": 2,
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "root_fingerprint": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "credential_id": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "backup_verified": {
                      "type": "boolean"
                    },
                    "why": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "state",
                    "chain",
                    "root_fingerprint",
                    "credential_id",
                    "backup_verified",
                    "why"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts": {
      "get": {
        "operationId": "listContacts",
        "summary": "Contacts with their tier and switchboard",
        "description": "Requires the `batondeck:contacts:read` permission (action `contact:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "description": "only contacts in this state",
              "type": "string",
              "enum": [
                "active",
                "pending_in",
                "pending_out",
                "blocked"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacts with their tier and switchboard",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contacts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "fingerprint": {
                            "type": "string"
                          },
                          "display_name": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "active",
                              "pending_in",
                              "pending_out",
                              "blocked"
                            ]
                          },
                          "preset": {
                            "type": "string"
                          },
                          "permissions": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "trust_flag": {
                            "type": "string"
                          },
                          "petname": {
                            "type": "string"
                          },
                          "last_seen_at": {
                            "type": "number"
                          },
                          "created_at": {
                            "type": "number"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "leaf": {
                            "type": "string"
                          },
                          "root_cert": {
                            "type": "string"
                          },
                          "address_claim": {
                            "anyOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "root": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "root",
                                  "name"
                                ],
                                "additionalProperties": false
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "acceptance_unheard_since": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "fingerprint",
                          "display_name",
                          "status",
                          "preset",
                          "permissions",
                          "trust_flag",
                          "petname",
                          "last_seen_at",
                          "created_at",
                          "endpoint",
                          "address_claim",
                          "acceptance_unheard_since"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "contacts"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "requestContact",
        "summary": "Ask the holder of a contact card to be a contact of this identity (SPEC §5.2, the manual flow)",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Ask the holder of a contact card to be a contact of this identity (SPEC §5.2, the manual flow)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "active"
                      ]
                    },
                    "contact": {
                      "type": "object",
                      "properties": {
                        "fingerprint": {
                          "type": "string"
                        },
                        "endpoint": {
                          "type": "string"
                        },
                        "display_name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "fingerprint",
                        "endpoint",
                        "display_name"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "status",
                    "contact"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "card": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 16384
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 1024
                  }
                },
                "required": [
                  "card"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}": {
      "patch": {
        "operationId": "updateContact",
        "summary": "Set what a contact may do: a preset, or the switches one by one",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Set what a contact may do: a preset, or the switches one by one",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "contact": {
                      "type": "object",
                      "properties": {
                        "fingerprint": {
                          "type": "string"
                        },
                        "display_name": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "active",
                            "pending_in",
                            "pending_out",
                            "blocked"
                          ]
                        },
                        "preset": {
                          "type": "string"
                        },
                        "permissions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "trust_flag": {
                          "type": "string"
                        },
                        "petname": {
                          "type": "string"
                        },
                        "last_seen_at": {
                          "type": "number"
                        },
                        "created_at": {
                          "type": "number"
                        },
                        "endpoint": {
                          "type": "string"
                        },
                        "leaf": {
                          "type": "string"
                        },
                        "root_cert": {
                          "type": "string"
                        },
                        "address_claim": {
                          "anyOf": [
                            {
                              "type": "object",
                              "properties": {
                                "root": {
                                  "type": "string"
                                },
                                "name": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "root",
                                "name"
                              ],
                              "additionalProperties": false
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "acceptance_unheard_since": {
                          "anyOf": [
                            {
                              "type": "number"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        }
                      },
                      "required": [
                        "fingerprint",
                        "display_name",
                        "status",
                        "preset",
                        "permissions",
                        "trust_flag",
                        "petname",
                        "last_seen_at",
                        "created_at",
                        "endpoint",
                        "address_claim",
                        "acceptance_unheard_since"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "contact"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "preset": {
                    "type": "string",
                    "enum": [
                      "basic",
                      "colleague",
                      "close",
                      "muted"
                    ]
                  },
                  "permissions": {
                    "maxItems": 64,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 96
                    }
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeContact",
        "summary": "Remove a contact: the pin goes on both sides and they are told (SPEC §5.3)",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Remove a contact: the pin goes on both sides and they are told (SPEC §5.3)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "removed"
                      ]
                    },
                    "fingerprint": {
                      "type": "string"
                    },
                    "notified": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "status",
                    "fingerprint",
                    "notified"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/approve": {
      "post": {
        "operationId": "approveContact",
        "summary": "Approve a contact request and set the preset it starts on",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.\n\nAccepts an `Idempotency-Key` header. A repeat within 24 hours returns the first answer; the same key with different arguments is refused with `idempotency_mismatch`.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repeat this value to retry the call without repeating its effect."
          }
        ],
        "responses": {
          "200": {
            "description": "Approve a contact request and set the preset it starts on",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "approved"
                      ]
                    },
                    "fingerprint": {
                      "type": "string"
                    },
                    "preset": {
                      "type": "string"
                    },
                    "notified": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "status",
                    "fingerprint",
                    "preset",
                    "notified"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That Idempotency-Key was used with different arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "preset": {
                    "default": "basic",
                    "type": "string",
                    "enum": [
                      "basic",
                      "colleague",
                      "close",
                      "muted"
                    ]
                  }
                },
                "required": [
                  "preset"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/reject": {
      "post": {
        "operationId": "rejectContact",
        "summary": "Reject a contact request: they are blocked, so they cannot knock again, and they are told (SPEC §5.1)",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.\n\nAccepts an `Idempotency-Key` header. A repeat within 24 hours returns the first answer; the same key with different arguments is refused with `idempotency_mismatch`.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repeat this value to retry the call without repeating its effect."
          }
        ],
        "responses": {
          "200": {
            "description": "Reject a contact request: they are blocked, so they cannot knock again, and they are told (SPEC §5.1)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "rejected"
                      ]
                    },
                    "fingerprint": {
                      "type": "string"
                    },
                    "notified": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "status",
                    "fingerprint",
                    "notified"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That Idempotency-Key was used with different arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/tell-accepted": {
      "post": {
        "operationId": "notifyAcceptance",
        "summary": "Tell an active contact again that we accepted them (SPEC §5.1), when the approval did not reach them",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.\n\nAccepts an `Idempotency-Key` header. A repeat within 24 hours returns the first answer; the same key with different arguments is refused with `idempotency_mismatch`.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repeat this value to retry the call without repeating its effect."
          }
        ],
        "responses": {
          "200": {
            "description": "Tell an active contact again that we accepted them (SPEC §5.1), when the approval did not reach them",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "fingerprint": {
                      "type": "string"
                    },
                    "notified": {
                      "type": "boolean"
                    },
                    "refusal": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "fingerprint",
                    "notified",
                    "refusal"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That Idempotency-Key was used with different arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/tools": {
      "get": {
        "operationId": "listContactTools",
        "summary": "The tools a contact offers us, asked of them over the sealed handshake",
        "description": "Requires the `batondeck:contacts:read` permission (action `contact:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tools a contact offers us, asked of them over the sealed handshake",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tools": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          },
                          "input_schema": {
                            "type": "object",
                            "propertyNames": {
                              "type": "string"
                            },
                            "additionalProperties": {}
                          }
                        },
                        "required": [
                          "name",
                          "description",
                          "input_schema"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "tools"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/call": {
      "post": {
        "operationId": "callContactTool",
        "summary": "Call one of a contact's tools as this identity (SPEC §5.4)",
        "description": "Requires the `batondeck:messages:send` permission (action `message:send`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Call one of a contact's tools as this identity (SPEC §5.4)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "result": {}
                  },
                  "required": [
                    "ok",
                    "result"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tool": {
                    "type": "string",
                    "minLength": 1
                  },
                  "arguments": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "tool"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/block": {
      "post": {
        "operationId": "blockContact",
        "summary": "Block a contact; the block is silent to them (SPEC §5.5)",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.\n\nAccepts an `Idempotency-Key` header. A repeat within 24 hours returns the first answer; the same key with different arguments is refused with `idempotency_mismatch`.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repeat this value to retry the call without repeating its effect."
          }
        ],
        "responses": {
          "200": {
            "description": "Block a contact; the block is silent to them (SPEC §5.5)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "blocked"
                      ]
                    },
                    "fingerprint": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "status",
                    "fingerprint"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That Idempotency-Key was used with different arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/unblock": {
      "post": {
        "operationId": "unblockContact",
        "summary": "Undo a block: a former contact returns to active as they were; a declined request is forgotten (SPEC §5)",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Undo a block: a former contact returns to active as they were; a declined request is forgotten (SPEC §5)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "active",
                        "forgotten"
                      ]
                    },
                    "fingerprint": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "status",
                    "fingerprint"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/petname": {
      "patch": {
        "operationId": "setPetname",
        "summary": "The owner's own private name for a contact",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The owner's own private name for a contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "petname": {
                    "type": "string",
                    "maxLength": 64
                  }
                },
                "required": [
                  "petname"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/refresh": {
      "post": {
        "operationId": "refreshContact",
        "summary": "Re-fetch ONE contact's signed card now, and say what was found (HDTP §14.3)",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Re-fetch ONE contact's signed card now, and say what was found (HDTP §14.3)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "outcome": {
                      "type": "string",
                      "enum": [
                        "unchanged",
                        "updated",
                        "renewed",
                        "unreachable",
                        "refused"
                      ]
                    },
                    "why": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "outcome"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/contacts/{fingerprint}/trust": {
      "patch": {
        "operationId": "setTrust",
        "summary": "Whether this contact may instruct, or only send messages (SPEC §6.2)",
        "description": "Requires the `batondeck:contacts:trust` permission (action `contact:trust`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fingerprint",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Whether this contact may instruct, or only send messages (SPEC §6.2)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "trust": {
                    "type": "string",
                    "enum": [
                      "messages_only",
                      "may_instruct"
                    ]
                  }
                },
                "required": [
                  "trust"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/pending/{id}": {
      "post": {
        "operationId": "answerPendingRequest",
        "summary": "Answer a request an integration parked for a person (SPEC §6.8)",
        "description": "Requires the `batondeck:requests:answer` permission (action `requests:answer`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Answer a request an integration parked for a person (SPEC §6.8)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "relayed": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "relayed"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "answer": {
                    "description": "the reply payload"
                  }
                },
                "required": [
                  "answer"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/pending": {
      "get": {
        "operationId": "listPendingRequests",
        "summary": "Agent-answered requests a caller is waiting on (SPEC §6.8)",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agent-answered requests a caller is waiting on (SPEC §6.8)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "requests": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "contact_fpr": {
                            "type": "string"
                          },
                          "capability": {
                            "type": "string"
                          },
                          "args": {
                            "type": "string"
                          },
                          "trust_flag": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "number"
                          },
                          "expires_at": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "id",
                          "contact_fpr",
                          "capability",
                          "args",
                          "trust_flag",
                          "created_at",
                          "expires_at"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "requests"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/invites": {
      "get": {
        "operationId": "listInvites",
        "summary": "Invites this identity has issued. Each carries its link where the token was kept, and only for a caller who may mint an invite (contact:write)",
        "description": "Requires the `batondeck:contacts:read` permission (action `contact:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invites this identity has issued. Each carries its link where the token was kept, and only for a caller who may mint an invite (contact:write)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "invites": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string"
                          },
                          "preset": {
                            "type": "string"
                          },
                          "uses": {
                            "type": "number"
                          },
                          "max_uses": {
                            "type": "number"
                          },
                          "auto_accept": {
                            "type": "boolean"
                          },
                          "expires_at": {
                            "type": "number"
                          },
                          "revoked_at": {
                            "anyOf": [
                              {
                                "type": "number"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          },
                          "url": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "label",
                          "preset",
                          "uses",
                          "max_uses",
                          "auto_accept",
                          "expires_at",
                          "revoked_at",
                          "url"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "invites"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createInvite",
        "summary": "Mint an invite. The link is in the answer, and listInvites answers it again, to a caller who may mint one, for as long as the row exists",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.\n\nAccepts an `Idempotency-Key` header. A repeat within 24 hours returns the first answer; the same key with different arguments is refused with `idempotency_mismatch`.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repeat this value to retry the call without repeating its effect."
          }
        ],
        "responses": {
          "201": {
            "description": "Mint an invite. The link is in the answer, and listInvites answers it again, to a caller who may mint one, for as long as the row exists",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "expires_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "id",
                    "url",
                    "expires_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That Idempotency-Key was used with different arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "default": "",
                    "type": "string",
                    "maxLength": 128
                  },
                  "preset": {
                    "default": "basic",
                    "type": "string",
                    "enum": [
                      "basic",
                      "colleague",
                      "close",
                      "muted"
                    ]
                  },
                  "max_uses": {
                    "description": "1 for one-time, 0 for unlimited",
                    "default": 1,
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000
                  },
                  "auto_accept": {
                    "default": false,
                    "type": "boolean"
                  },
                  "expires_in_days": {
                    "default": 14,
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 90
                  }
                },
                "required": [
                  "label",
                  "preset",
                  "max_uses",
                  "auto_accept",
                  "expires_in_days"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/invites/redeem": {
      "post": {
        "operationId": "redeemInvite",
        "summary": "Accept somebody else's invite link: this identity becomes their contact",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Accept somebody else's invite link: this identity becomes their contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "accepted",
                        "pending"
                      ]
                    },
                    "contact": {
                      "type": "object",
                      "properties": {
                        "fingerprint": {
                          "type": "string"
                        },
                        "endpoint": {
                          "type": "string"
                        },
                        "display_name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "fingerprint",
                        "endpoint",
                        "display_name"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "status",
                    "contact"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "minLength": 12,
                    "maxLength": 2048
                  }
                },
                "required": [
                  "url"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/invites/{id}": {
      "delete": {
        "operationId": "revokeInvite",
        "summary": "Revoke an invite; a revoked token is indistinguishable from one that never existed",
        "description": "Requires the `batondeck:contacts:manage` permission (action `contact:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Done. No body."
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/threads": {
      "get": {
        "operationId": "listThreads",
        "summary": "Message threads, newest first, with unread counts",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": true,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Message threads, newest first, with unread counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "threads": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "contact_fpr": {
                            "type": "string"
                          },
                          "topic": {
                            "type": "string"
                          },
                          "last_at": {
                            "type": "number"
                          },
                          "unread": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "id",
                          "contact_fpr",
                          "topic",
                          "last_at",
                          "unread"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "cursor": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "threads"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/threads/{threadId}": {
      "get": {
        "operationId": "getThread",
        "summary": "One thread by its id, whatever page it is on, with its unread count",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "threadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One thread by its id, whatever page it is on, with its unread count",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "contact_fpr": {
                      "type": "string"
                    },
                    "topic": {
                      "type": "string"
                    },
                    "last_at": {
                      "type": "number"
                    },
                    "unread": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "id",
                    "contact_fpr",
                    "topic",
                    "last_at",
                    "unread"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/inbox": {
      "get": {
        "operationId": "getInboxTotals",
        "summary": "Thread count, unread total and newest activity over every thread, counted by the object",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Thread count, unread total and newest activity over every thread, counted by the object",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "threads": {
                      "type": "number"
                    },
                    "unread": {
                      "type": "number"
                    },
                    "last_at": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "threads",
                    "unread",
                    "last_at"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/badges": {
      "get": {
        "operationId": "getIdentityBadges",
        "summary": "The portal shell's counts in one read: unread over every thread (counted up to a cap), unread per thread of the newest page, contact requests waiting, requests parked for a person",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The portal shell's counts in one read: unread over every thread (counted up to a cap), unread per thread of the newest page, contact requests waiting, requests parked for a person",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "unread": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9007199254740991
                        },
                        "capped": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "count",
                        "capped"
                      ],
                      "additionalProperties": false
                    },
                    "threads": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "contact_fpr": {
                            "type": "string"
                          },
                          "unread": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "id",
                          "contact_fpr",
                          "unread"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "pending_in": {
                      "anyOf": [
                        {
                          "type": "number"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "parked": {
                      "type": "number"
                    },
                    "names": {
                      "anyOf": [
                        {
                          "type": "object",
                          "propertyNames": {
                            "type": "string"
                          },
                          "additionalProperties": {
                            "type": "string"
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "unread",
                    "threads",
                    "pending_in",
                    "parked",
                    "names"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/threads/{threadId}/messages": {
      "get": {
        "operationId": "listMessages",
        "summary": "The newest messages in one thread, oldest first within the window",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "threadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": true,
            "schema": {
              "default": 50,
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The newest messages in one thread, oldest first within the window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "thread_id": {
                      "type": "string"
                    },
                    "trust": {
                      "type": "string"
                    },
                    "messages": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "thread_id": {
                            "type": "string"
                          },
                          "contact_fpr": {
                            "type": "string"
                          },
                          "direction": {
                            "type": "string",
                            "enum": [
                              "in",
                              "out"
                            ]
                          },
                          "sender": {
                            "type": "string",
                            "enum": [
                              "agent",
                              "human"
                            ]
                          },
                          "body": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "reply_to": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "number"
                          },
                          "expires_at": {
                            "type": "number"
                          },
                          "attempts": {
                            "type": "number"
                          },
                          "last_refusal": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "null"
                              }
                            ]
                          }
                        },
                        "required": [
                          "id",
                          "thread_id",
                          "contact_fpr",
                          "direction",
                          "sender",
                          "body",
                          "kind",
                          "reply_to",
                          "status",
                          "created_at",
                          "expires_at",
                          "attempts",
                          "last_refusal"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "thread_id",
                    "trust",
                    "messages"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/threads/{threadId}/read": {
      "post": {
        "operationId": "markThreadRead",
        "summary": "The owner has read this thread, through the message `through` names or, with no body, all of it (SPEC §7.6)",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "threadId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The owner has read this thread, through the message `through` names or, with no body, all of it (SPEC §7.6)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "thread_id": {
                      "type": "string"
                    },
                    "unread": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 9007199254740991
                    }
                  },
                  "required": [
                    "thread_id",
                    "unread"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "through": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/messages": {
      "post": {
        "operationId": "sendMessage",
        "summary": "Send a message to a contact as this identity",
        "description": "Requires the `batondeck:messages:send` permission (action `message:send`).\n\nRefused while the workspace is suspended or on deletion hold.\n\nAccepts an `Idempotency-Key` header. A repeat within 24 hours returns the first answer; the same key with different arguments is refused with `idempotency_mismatch`.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repeat this value to retry the call without repeating its effect."
          }
        ],
        "responses": {
          "201": {
            "description": "Send a message to a contact as this identity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "message_id": {
                      "type": "string"
                    },
                    "thread_id": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "status",
                    "message_id",
                    "thread_id"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That Idempotency-Key was used with different arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fingerprint": {
                    "type": "string",
                    "minLength": 1
                  },
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 16384
                  },
                  "thread_id": {
                    "type": "string",
                    "maxLength": 128
                  },
                  "msg_id": {
                    "type": "string",
                    "maxLength": 128
                  }
                },
                "required": [
                  "fingerprint",
                  "text"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/messages/{id}/retry": {
      "post": {
        "operationId": "retryMessage",
        "summary": "Try again now to deliver an outbound text message that has not arrived (pending, or given up on)",
        "description": "Requires the `batondeck:messages:send` permission (action `message:send`).\n\nRefused while the workspace is suspended or on deletion hold.\n\nAccepts an `Idempotency-Key` header. A repeat within 24 hours returns the first answer; the same key with different arguments is refused with `idempotency_mismatch`.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repeat this value to retry the call without repeating its effect."
          }
        ],
        "responses": {
          "200": {
            "description": "Try again now to deliver an outbound text message that has not arrived (pending, or given up on)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "message_id": {
                      "type": "string"
                    },
                    "thread_id": {
                      "type": "string"
                    },
                    "attempts": {
                      "type": "number"
                    },
                    "last_refusal": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "status",
                    "message_id",
                    "thread_id",
                    "attempts",
                    "last_refusal"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That Idempotency-Key was used with different arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/media": {
      "post": {
        "operationId": "sendMedia",
        "summary": "Send a file to a contact (SPEC §7.4)",
        "description": "Requires the `batondeck:messages:send` permission (action `message:send`).\n\nRefused while the workspace is suspended or on deletion hold.\n\nAccepts an `Idempotency-Key` header. A repeat within 24 hours returns the first answer; the same key with different arguments is refused with `idempotency_mismatch`.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Repeat this value to retry the call without repeating its effect."
          }
        ],
        "responses": {
          "201": {
            "description": "Send a file to a contact (SPEC §7.4)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string"
                    },
                    "message_id": {
                      "type": "string"
                    },
                    "thread_id": {
                      "type": "string"
                    },
                    "hash": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "status",
                    "message_id",
                    "thread_id",
                    "hash"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "That Idempotency-Key was used with different arguments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This identity's outbound budget (HDTP §12: 1 call a second per contact with a burst of 10, the identity's aggregate, 20 an hour to strangers), or the peer's own, refused the call; `retry_after` and Retry-After say when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fingerprint": {
                    "type": "string",
                    "minLength": 1
                  },
                  "data": {
                    "type": "string",
                    "minLength": 1
                  },
                  "filename": {
                    "default": "",
                    "type": "string",
                    "maxLength": 256
                  },
                  "mime": {
                    "default": "application/octet-stream",
                    "type": "string",
                    "maxLength": 128
                  },
                  "thread_id": {
                    "type": "string",
                    "maxLength": 128
                  },
                  "msg_id": {
                    "type": "string",
                    "maxLength": 128
                  }
                },
                "required": [
                  "fingerprint",
                  "data",
                  "filename",
                  "mime"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/changes": {
      "get": {
        "operationId": "watchChanges",
        "summary": "What has happened since a cursor, waiting up to 25 seconds for it to",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": true,
            "schema": {
              "description": "cursor from a previous answer; 0 starts from now with no backlog",
              "default": 0,
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            }
          },
          {
            "name": "wait",
            "in": "query",
            "required": true,
            "schema": {
              "description": "seconds to wait for something to happen, from 1 to 25",
              "default": 25,
              "type": "integer",
              "minimum": 1,
              "maximum": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What has happened since a cursor, waiting up to 25 seconds for it to",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "cursor": {
                      "type": "number"
                    },
                    "threads": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "propertyNames": {
                          "type": "string"
                        },
                        "additionalProperties": {}
                      }
                    },
                    "contact_requests": {
                      "type": "number"
                    },
                    "contact_requests_new": {
                      "type": "number"
                    },
                    "pending_requests": {
                      "type": "number"
                    },
                    "pending_requests_new": {
                      "type": "number"
                    },
                    "calls": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "propertyNames": {
                          "type": "string"
                        },
                        "additionalProperties": {}
                      }
                    },
                    "needs_attention": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "propertyNames": {
                          "type": "string"
                        },
                        "additionalProperties": {}
                      }
                    },
                    "calls_truncated": {
                      "type": "boolean"
                    },
                    "timed_out": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "cursor",
                    "threads",
                    "contact_requests",
                    "contact_requests_new",
                    "pending_requests",
                    "pending_requests_new",
                    "calls",
                    "needs_attention",
                    "calls_truncated",
                    "timed_out"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/digest": {
      "get": {
        "operationId": "getDigest",
        "summary": "A per-contact summary of the last day, for an agent catching up",
        "description": "Requires the `batondeck:messages:read` permission (action `message:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": true,
            "schema": {
              "default": 0,
              "type": "integer",
              "minimum": 0,
              "maximum": 9007199254740991
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A per-contact summary of the last day, for an agent catching up",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/integrations": {
      "get": {
        "operationId": "listIntegrations",
        "summary": "The integrations this identity has connected, and whether each is healthy",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The integrations this identity has connected, and whether each is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "integrations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "slug": {
                            "type": "string"
                          },
                          "transport": {
                            "type": "string"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "permission": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "slug",
                          "transport",
                          "endpoint",
                          "status",
                          "permission"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "integrations"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createIntegration",
        "summary": "Connect an MCP server: a catalogue entry in one click, or any server by its URL (SPEC §6.1-§6.4)",
        "description": "Requires the `batondeck:identities:manage` permission (action `integration:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Connect an MCP server: a catalogue entry in one click, or any server by its URL (SPEC §6.1-§6.4)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "integration": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "slug": {
                          "type": "string"
                        },
                        "transport": {
                          "type": "string"
                        },
                        "endpoint": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string"
                        },
                        "permission": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "slug",
                        "transport",
                        "endpoint",
                        "status",
                        "permission"
                      ],
                      "additionalProperties": false
                    },
                    "tools": {
                      "type": "number"
                    },
                    "trouble": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "authorization_url": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "integration",
                    "tools",
                    "trouble",
                    "authorization_url"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "catalogue": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 32
                  },
                  "endpoint": {
                    "type": "string",
                    "format": "uri"
                  },
                  "transport": {
                    "type": "string",
                    "enum": [
                      "streamable-http",
                      "sse"
                    ]
                  },
                  "auth": {
                    "anyOf": [
                      {
                        "type": "object",
                        "properties": {
                          "header": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64
                          },
                          "value": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 4096
                          }
                        },
                        "required": [
                          "header",
                          "value"
                        ],
                        "additionalProperties": false
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/integrations/catalogue": {
      "get": {
        "operationId": "listIntegrationCatalogue",
        "summary": "The MCP servers that connect in one click: each id, name, category, one line, endpoint and icon",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:list`).",
        "tags": [
          "workspace"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "The MCP servers that connect in one click: each id, name, category, one line, endpoint and icon",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "servers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string"
                          },
                          "summary": {
                            "type": "string"
                          },
                          "endpoint": {
                            "type": "string"
                          },
                          "icon": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "name",
                          "category",
                          "summary",
                          "endpoint",
                          "icon"
                        ],
                        "additionalProperties": false
                      }
                    }
                  },
                  "required": [
                    "servers"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/integrations/{name}/authorize": {
      "post": {
        "operationId": "authorizeIntegration",
        "summary": "Start the upstream OAuth ceremony and hand back the URL to send the owner to",
        "description": "Requires the `batondeck:identities:manage` permission (action `integration:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Start the upstream OAuth ceremony and hand back the URL to send the owner to",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "authorization_url": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "authorization_url"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "maxLength": 512
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/integrations/{name}/exposure": {
      "get": {
        "operationId": "getExposure",
        "summary": "What this integration offers, and what it could offer (SPEC §6.5)",
        "description": "Requires the `batondeck:identities:read` permission (action `identity:read`).",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "What this integration offers, and what it could offer (SPEC §6.5)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "catalog": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "propertyNames": {
                          "type": "string"
                        },
                        "additionalProperties": {}
                      }
                    },
                    "entries": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "propertyNames": {
                          "type": "string"
                        },
                        "additionalProperties": {}
                      }
                    },
                    "version": {
                      "type": "number"
                    },
                    "stale": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "catalog",
                    "entries",
                    "version",
                    "stale"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "setExposure",
        "summary": "Replace which of an integration's tools contacts may reach",
        "description": "Requires the `batondeck:identities:manage` permission (action `integration:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Replace which of an integration's tools contacts may reach",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "version": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "version"
                  ],
                  "additionalProperties": false
                }
              }
            }
          },
          "400": {
            "description": "The arguments did not validate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "entries": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "tool": {
                          "type": "string"
                        },
                        "mode": {
                          "type": "string",
                          "enum": [
                            "passthrough",
                            "mapped",
                            "agent"
                          ]
                        }
                      },
                      "required": [
                        "tool",
                        "mode"
                      ],
                      "additionalProperties": {}
                    }
                  }
                },
                "required": [
                  "entries"
                ],
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/v1/identities/{slug}/integrations/{name}": {
      "delete": {
        "operationId": "deleteIntegration",
        "summary": "Disconnect an upstream and forget its credential",
        "description": "Requires the `batondeck:identities:manage` permission (action `integration:write`).\n\nRefused while the workspace is suspended or on deletion hold.",
        "tags": [
          "identity"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Disconnect an upstream and forget its credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "null"
                }
              }
            }
          },
          "401": {
            "description": "No portal session, and no live API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The policy refused, or the request was cross-site.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such resource, or none this session may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "description": "a stable machine-readable code; HDTP §12 names where a call maps to one",
                "type": "string"
              },
              "message": {
                "description": "what went wrong, in words a person can act on",
                "type": "string"
              },
              "request_id": {
                "description": "the same id that appears in our logs for this request",
                "type": "string"
              },
              "retry_after": {
                "description": "on a 429: seconds until the window closes, as the Retry-After header says",
                "type": "integer",
                "minimum": -9007199254740991,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "code",
              "message",
              "request_id"
            ],
            "additionalProperties": false
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "session": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__Host-bd_session"
      },
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A workspace API key: `bd_<id>_<secret>`. Minted by a person on the portal's Agents page (Agent keys), which calls `POST /v1/workspace/keys` with a session; a key cannot mint one, because minting needs step-up. Shown once, hashed at rest. Malformed, unknown and revoked keys answer identically."
      }
    }
  },
  "security": [
    {
      "session": []
    },
    {
      "apiKey": []
    }
  ]
}
