{
  "openapi": "3.1.0",
  "info": {
    "title": "Blastack API",
    "version": "1.1.0",
    "summary": "Internal communication for companies whose staff have no desk.",
    "description": "Read your workforce, publish to the wall, pull the figures, and get called when something happens.\n\n**Authentication.** One header: `Authorization: Bearer blsk_…`. Keys are issued by us — there is no self-service screen — and each one is tied to exactly one company and carries a list of scopes. No endpoint reads a company id from the request, so there is nothing to get wrong there.\n\n**Rate limit.** 120 requests per minute per key. Every reply carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`; a 429 also carries `Retry-After`.\n\n**Errors** always have the same shape and a stable `code`. Branch on the code, never on the message.\n\n**Unknown parameters are refused,** not ignored. A typo in a filter name comes back as a 400 that lists the ones this endpoint takes, because a filter that is silently dropped hands you more data than you asked for and nothing tells you.\n\n**Outgoing events (webhooks)** are described under `webhooks` below. Register an endpoint with `POST /webhooks` and we call you within a minute of something happening — which is what Zapier, Make and n8n need to trigger a flow without polling us every five minutes.\n\n**Module.** The API belongs to the corporate identity module, the same one that opens single sign-on and SCIM provisioning.",
    "contact": {
      "url": "https://blastack.com/en/developers"
    }
  },
  "servers": [
    {
      "url": "https://app.blastack.com/api/v1",
      "description": "Production. There is no separate sandbox."
    }
  ],
  "tags": [
    {
      "name": "people",
      "description": "The workforce."
    },
    {
      "name": "audiences",
      "description": "Departments and sites."
    },
    {
      "name": "announcements",
      "description": "The wall."
    },
    {
      "name": "metrics",
      "description": "Aggregated figures."
    },
    {
      "name": "webhooks",
      "description": "Where we call you."
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "blsk_<prefix>_<secret>",
        "description": "An API key, issued per company. Scopes available: people:read, people:write, announcements:read, announcements:write, metrics:read, webhooks:read, webhooks:write. A key only ever sees its own company."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "status": {
                "type": "integer"
              },
              "code": {
                "type": "string",
                "enum": [
                  "invalid_token",
                  "insufficient_scope",
                  "module_required",
                  "read_only",
                  "rate_limited",
                  "invalid_request",
                  "not_found",
                  "conflict",
                  "server_error"
                ],
                "description": "Machine-readable and stable. Branch on this, never on the message."
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "status",
              "code",
              "message"
            ]
          },
          "documentation": {
            "type": "string"
          }
        }
      }
    }
  },
  "paths": {
    "/people": {
      "get": {
        "operationId": "listPeople",
        "summary": "List people",
        "description": "The company's workforce, with filters and paging. This is not the same surface as SCIM: /scim/v2/Users is written by your identity provider and its shape is dictated by that standard, while this one is plain JSON meant for a script or a dashboard. Phone number and date of birth are deliberately never returned.",
        "tags": [
          "people"
        ],
        "security": [
          {
            "apiKey": [
              "people:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page to return. The first page is 1, not 0.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 2
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "example": 100
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Field to sort by. Defaults to created_at.",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "name",
                "email"
              ],
              "default": "created_at"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of fields to return. `id` is always included. Use it to avoid pulling personal data you do not need.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "external_id",
                  "email",
                  "name",
                  "job_title",
                  "department",
                  "center",
                  "role",
                  "status",
                  "language",
                  "created_at",
                  "updated_at"
                ]
              }
            },
            "example": [
              "external_id",
              "department",
              "status"
            ]
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only people in these states. Several may be given.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "active",
                  "invited",
                  "disabled"
                ]
              }
            },
            "example": [
              "active"
            ]
          },
          {
            "name": "role",
            "in": "query",
            "required": false,
            "description": "Only people with these roles. Several may be given.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "owner",
                  "admin",
                  "gestor_personas",
                  "responsable_local",
                  "informatico",
                  "editor",
                  "revisor",
                  "analista",
                  "employee"
                ]
              }
            }
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "description": "Department name, or several separated by commas. Case-insensitive: your directory writing MANTENIMIENTO is not an error.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "example": [
              "Maintenance",
              "Quality"
            ]
          },
          {
            "name": "center",
            "in": "query",
            "required": false,
            "description": "Site name, or several separated by commas. Case-insensitive.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "no_department",
            "in": "query",
            "required": false,
            "description": "Only people with no department. These are the ones who never see a targeted announcement, so this is the filter that finds a broken provisioning mapping.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "language",
            "in": "query",
            "required": false,
            "description": "Only people who picked this reading language.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "es",
                  "ca",
                  "en",
                  "pt",
                  "fr",
                  "it",
                  "de",
                  "sv",
                  "nl",
                  "pl",
                  "ro",
                  "cs"
                ]
              }
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Exact email address, case-insensitive.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "required": false,
            "description": "Exact external_id, as sent by your HR system over SCIM.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free text over name, email and job title. Case-insensitive, matches anywhere in the value.",
            "schema": {
              "type": "string"
            },
            "example": "garcia"
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Only people changed at or after this instant. This is the one that lets you sync every fifteen minutes without pulling two thousand rows every time.",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-08-01T00:00:00Z"
          },
          {
            "name": "created_since",
            "in": "query",
            "required": false,
            "description": "Only people added at or after this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_until",
            "in": "query",
            "required": false,
            "description": "Only people added before this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "external_id": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "job_title": {
                            "type": "string"
                          },
                          "department": {
                            "type": "string"
                          },
                          "center": {
                            "type": "string"
                          },
                          "role": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "language": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total number of items across all pages."
                    }
                  },
                  "required": [
                    "data",
                    "page",
                    "per_page",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPerson",
        "summary": "Add a person",
        "description": "Creates someone in the company. Use this when your HR system does not speak SCIM — if it does, use SCIM: it also removes people when they leave, which is the half that matters. The department and the site are given by NAME and are created if they do not exist yet, exactly like SCIM does: a real person is arriving and has to go somewhere. No invitation email is sent; Blastack only uses email for billing.",
        "tags": [
          "people"
        ],
        "security": [
          {
            "apiKey": [
              "people:write"
            ]
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "description": "Full name, as it should appear on the wall.",
                    "type": "string",
                    "maxLength": 120
                  },
                  "email": {
                    "description": "Work email. Optional: most of the workforce this product is for has none, and they get in with an invite code instead. Unique within the company.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "external_id": {
                    "description": "The id this person has in your system. Store it and you can cross both lists later without matching names.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "job_title": {
                    "description": "Job title.",
                    "type": "string",
                    "maxLength": 120
                  },
                  "department": {
                    "description": "Department name. Created if it does not exist. Without one, this person only ever sees company-wide announcements.",
                    "type": "string",
                    "maxLength": 80
                  },
                  "center": {
                    "description": "Site name. Created if it does not exist.",
                    "type": "string",
                    "maxLength": 80
                  },
                  "role": {
                    "description": "What they can do. `owner` cannot be given from the API: a key pasted in a cron job must not be able to mint an account that can cancel the subscription.",
                    "type": "string",
                    "enum": [
                      "admin",
                      "gestor_personas",
                      "responsable_local",
                      "informatico",
                      "editor",
                      "revisor",
                      "analista",
                      "employee"
                    ],
                    "default": "employee"
                  },
                  "status": {
                    "description": "`active` can sign in; `invited` is created but not counted as signed up yet; `disabled` is created already deactivated, which is what a backfill of former staff needs.",
                    "type": "string",
                    "enum": [
                      "active",
                      "invited",
                      "disabled"
                    ],
                    "default": "active"
                  },
                  "language": {
                    "description": "Reading language. Leave it out and they read in the company's, which is what you want unless you actually know.",
                    "type": "string",
                    "enum": [
                      "es",
                      "ca",
                      "en",
                      "pt",
                      "fr",
                      "it",
                      "de",
                      "sv",
                      "nl",
                      "pl",
                      "ro",
                      "cs"
                    ]
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": false
              },
              "example": {
                "name": "Ana García",
                "external_id": "EMP-0042"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "external_id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "job_title": {
                      "type": "string"
                    },
                    "department": {
                      "type": "string"
                    },
                    "center": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "language": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It clashes with something that already exists. The reply carries the id of what it clashed with.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/people/{id}": {
      "get": {
        "operationId": "getPerson",
        "summary": "Get one person",
        "description": "Refresh a single record without paging through the whole workforce. Accepts either our id or the external_id your identity provider sends us, so you do not have to keep a mapping table.",
        "tags": [
          "people"
        ],
        "security": [
          {
            "apiKey": [
              "people:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Blastack id (a uuid) or the person's external_id from your HR system. A Blastack id is always a uuid and yours almost never is, so they cannot be confused; if one ever were, ours wins.",
            "schema": {
              "type": "string"
            },
            "example": "a3f1…"
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of fields to return. `id` is always included. Use it to avoid pulling personal data you do not need.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "external_id",
                  "email",
                  "name",
                  "job_title",
                  "department",
                  "center",
                  "role",
                  "status",
                  "language",
                  "created_at",
                  "updated_at"
                ]
              }
            },
            "example": [
              "external_id",
              "department",
              "status"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "external_id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "job_title": {
                      "type": "string"
                    },
                    "department": {
                      "type": "string"
                    },
                    "center": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "language": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updatePerson",
        "summary": "Update a person",
        "description": "Changes only the fields you send. A field you leave out is not touched: a sync that only knows the job title cannot wipe someone's site. Send an empty string to clear a text field. Setting status to `disabled` deactivates them and closes their open sessions.",
        "tags": [
          "people"
        ],
        "security": [
          {
            "apiKey": [
              "people:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Blastack id (a uuid) or the person's external_id from your HR system. A Blastack id is always a uuid and yours almost never is, so they cannot be confused; if one ever were, ours wins.",
            "schema": {
              "type": "string"
            },
            "example": "a3f1…"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "description": "Full name.",
                    "type": "string",
                    "maxLength": 120
                  },
                  "email": {
                    "description": "Work email.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "external_id": {
                    "description": "The id this person has in your system.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "job_title": {
                    "description": "Job title.",
                    "type": "string",
                    "maxLength": 120
                  },
                  "department": {
                    "description": "Department name. Created if it does not exist.",
                    "type": "string",
                    "maxLength": 80
                  },
                  "center": {
                    "description": "Site name. Created if it does not exist.",
                    "type": "string",
                    "maxLength": 80
                  },
                  "role": {
                    "description": "What they can do. `owner` cannot be given from the API.",
                    "type": "string",
                    "enum": [
                      "admin",
                      "gestor_personas",
                      "responsable_local",
                      "informatico",
                      "editor",
                      "revisor",
                      "analista",
                      "employee"
                    ]
                  },
                  "status": {
                    "description": "Their state. `disabled` also closes their sessions.",
                    "type": "string",
                    "enum": [
                      "active",
                      "invited",
                      "disabled"
                    ]
                  },
                  "language": {
                    "description": "Reading language.",
                    "type": "string",
                    "enum": [
                      "es",
                      "ca",
                      "en",
                      "pt",
                      "fr",
                      "it",
                      "de",
                      "sv",
                      "nl",
                      "pl",
                      "ro",
                      "cs"
                    ]
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "external_id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "job_title": {
                      "type": "string"
                    },
                    "department": {
                      "type": "string"
                    },
                    "center": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "language": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It clashes with something that already exists. The reply carries the id of what it clashed with.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deactivatePerson",
        "summary": "Deactivate a person",
        "description": "DEACTIVATES; it never deletes. Their sessions are closed straight away, so someone let go on Friday is out on Friday. What they posted stays, because a wall full of holes helps nobody. Deleting for real is a written request handled by us, and it anonymises rather than removes. Calling this on someone already deactivated is not an error.",
        "tags": [
          "people"
        ],
        "security": [
          {
            "apiKey": [
              "people:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Blastack id (a uuid) or the person's external_id from your HR system. A Blastack id is always a uuid and yours almost never is, so they cannot be confused; if one ever were, ours wins.",
            "schema": {
              "type": "string"
            },
            "example": "a3f1…"
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "external_id": {
                      "type": "string"
                    },
                    "email": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "job_title": {
                      "type": "string"
                    },
                    "department": {
                      "type": "string"
                    },
                    "center": {
                      "type": "string"
                    },
                    "role": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "language": {
                      "type": "string"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/audiences": {
      "get": {
        "operationId": "listAudiences",
        "summary": "List departments and sites",
        "description": "Who an announcement can be aimed at. Without this you cannot use the audience field of POST /announcements: you need to know what these places are called HERE. Archived ones are never listed, because publishing to them is refused three lines later.",
        "tags": [
          "audiences"
        ],
        "security": [
          {
            "apiKey": [
              "people:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Return only departments, only sites, or both.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "departments",
                "centers"
              ],
              "default": "all"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free text over the name. Case-insensitive.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "with_people",
            "in": "query",
            "required": false,
            "description": "Only groups that have at least one active person. A department with nobody in it is almost always an old name left behind, and it is the usual cause of an announcement that reaches no one.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Field to sort by. Defaults to name.",
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "people"
              ],
              "default": "name"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "departments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "city": {
                            "type": "string"
                          },
                          "people": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "centers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "city": {
                            "type": "string"
                          },
                          "people": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "departments",
                    "centers"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/announcements": {
      "get": {
        "operationId": "listAnnouncements",
        "summary": "List announcements",
        "description": "Everything posted on the wall, whatever route it arrived by, with the read count and the acknowledgement count on each one. Drafts and rejected posts are included so a dashboard can show the whole funnel; filter by status if you only want what is live.",
        "tags": [
          "announcements"
        ],
        "security": [
          {
            "apiKey": [
              "announcements:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page to return. The first page is 1, not 0.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 2
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "example": 100
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Field to sort by. Defaults to published_at.",
            "schema": {
              "type": "string",
              "enum": [
                "published_at",
                "created_at",
                "updated_at",
                "title"
              ],
              "default": "published_at"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of fields to return. `id` is always included. Use it to avoid pulling personal data you do not need.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "title",
                  "body",
                  "status",
                  "kind",
                  "language",
                  "audience",
                  "community",
                  "section",
                  "author",
                  "notify",
                  "comments_enabled",
                  "pinned",
                  "acknowledgements",
                  "reads",
                  "published_at",
                  "created_at",
                  "updated_at",
                  "url"
                ]
              }
            },
            "example": [
              "external_id",
              "department",
              "status"
            ]
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only these states. Several may be given. `scheduled` means it is written and waiting for its hour.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "draft",
                  "pending_review",
                  "published",
                  "rejected",
                  "scheduled",
                  "archived"
                ]
              }
            },
            "example": [
              "published",
              "scheduled"
            ]
          },
          {
            "name": "author",
            "in": "query",
            "required": false,
            "description": "Who signs it: our id, their email or their external_id. Any of the three.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "audience_type",
            "in": "query",
            "required": false,
            "description": "Only announcements aimed this way.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "departments",
                "centers"
              ]
            }
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "description": "Only announcements aimed at these departments, by name. Several may be given.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "center",
            "in": "query",
            "required": false,
            "description": "Only announcements aimed at these sites, by name.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "community",
            "in": "query",
            "required": false,
            "description": "Only announcements in this community, by id or by name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requires_acknowledgement",
            "in": "query",
            "required": false,
            "description": "Only required reading, or only the rest. This is the filter a compliance report is built on.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "pinned",
            "in": "query",
            "required": false,
            "description": "Only pinned announcements, or only the rest.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Free text over title and body. Case-insensitive.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "published_since",
            "in": "query",
            "required": false,
            "description": "Published at or after this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "published_until",
            "in": "query",
            "required": false,
            "description": "Published before this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "created_since",
            "in": "query",
            "required": false,
            "description": "Written at or after this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "description": "Changed at or after this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "body": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "language": {
                            "type": "string"
                          },
                          "audience": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string"
                              },
                              "ids": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "names": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            }
                          },
                          "community": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              }
                            }
                          },
                          "section": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              }
                            }
                          },
                          "author": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string"
                              },
                              "external_id": {
                                "type": "string"
                              }
                            }
                          },
                          "notify": {
                            "type": "boolean"
                          },
                          "comments_enabled": {
                            "type": "boolean"
                          },
                          "pinned": {
                            "type": "boolean"
                          },
                          "acknowledgements": {
                            "type": "object",
                            "properties": {
                              "required": {
                                "type": "boolean"
                              },
                              "confirmed": {
                                "type": "integer"
                              }
                            }
                          },
                          "reads": {
                            "type": "integer"
                          },
                          "published_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "url": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total number of items across all pages."
                    }
                  },
                  "required": [
                    "data",
                    "page",
                    "per_page",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createAnnouncement",
        "summary": "Publish an announcement",
        "description": "The endpoint an API gets asked for. Your ERP already knows line 3 stops on Thursday: it can post it itself. Two things worth knowing before you write the call. First, you must say WHO SIGNS it, and there is no default: a wall with faceless posts is a wall people stop reading. Second, the company's review queue is respected — if the signer cannot publish without review, the announcement joins the queue and the reply says so with `status: pending_review`.",
        "tags": [
          "announcements"
        ],
        "security": [
          {
            "apiKey": [
              "announcements:write"
            ]
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "description": "The text. Plain text; line breaks are kept.",
                    "type": "string",
                    "maxLength": 8000
                  },
                  "author": {
                    "description": "Who signs it: our id, their email or their external_id. Must be an active person in this company.",
                    "type": "string"
                  },
                  "title": {
                    "description": "Headline.",
                    "type": "string",
                    "maxLength": 160
                  },
                  "audience": {
                    "description": "Who it goes to. Leave it out and it goes to everyone. Names work as well as ids, case-insensitively, because your ERP has 'Maintenance' and not a uuid of ours. Nothing is created here: an unknown name is a typo in a script, and the reply tells you which one it was.",
                    "type": "object",
                    "properties": {
                      "type": {
                        "description": "Aim at everyone, at departments or at sites.",
                        "type": "string",
                        "enum": [
                          "all",
                          "departments",
                          "centers"
                        ],
                        "default": "all"
                      },
                      "ids": {
                        "description": "Department or site names, or their ids. Required unless type is `all`.",
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "community": {
                    "description": "Which community it belongs to, by id or by name. Leave it out and it goes on the main wall, which everybody sees. The author has to belong to the community, or be posting to the default one: an API key is not a way into a community its signer is not in.",
                    "type": "string"
                  },
                  "section": {
                    "description": "Which section it belongs to, by id or by name. It must belong to the same community.",
                    "type": "string"
                  },
                  "publish_at": {
                    "description": "Publish later, at this instant. It must be in the future. The reply comes back with `status: scheduled` and the wall shows it when the hour arrives. Leave it out to publish now. Note this means two clocks are involved — yours and ours — so if your system already schedules, prefer calling us at the right moment.",
                    "type": "string",
                    "format": "date-time"
                  },
                  "requires_acknowledgement": {
                    "description": "Required reading: everyone it reaches gets a notification with a confirm button, and who confirmed is on record. Only signers who can publish without review may ask for it.",
                    "type": "boolean",
                    "default": false
                  },
                  "notify": {
                    "description": "Whether it makes a sound. Written the right way round on purpose: the screen has a 'publish without notifying' checkbox because a form that loses a field should still notify, but here you write the whole JSON and a field called `silent` reads wrong at three in the morning. Default is true.",
                    "type": "boolean",
                    "default": true
                  },
                  "comments_enabled": {
                    "description": "Whether people can comment on it.",
                    "type": "boolean",
                    "default": true
                  },
                  "pinned": {
                    "description": "Keep it at the top of the wall.",
                    "type": "boolean",
                    "default": false
                  },
                  "language": {
                    "description": "What language the text is written in. Leave it out and the company's is used. Getting this right matters: a Catalan original labelled as Spanish is shown to a Spanish reader without the 'not translated' notice that tells them nothing is broken.",
                    "type": "string",
                    "enum": [
                      "es",
                      "ca",
                      "en",
                      "pt",
                      "fr",
                      "it",
                      "de",
                      "sv",
                      "nl",
                      "pl",
                      "ro",
                      "cs"
                    ]
                  }
                },
                "required": [
                  "body",
                  "author"
                ],
                "additionalProperties": false
              },
              "example": {
                "author": "EMP-0042",
                "publish_at": "2026-09-01T06:00:00Z"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/announcements/{id}": {
      "get": {
        "operationId": "getAnnouncement",
        "summary": "Get one announcement",
        "description": "The same shape as one item of the list. Use it to follow up on the id you got back from POST /announcements or from an outgoing event.",
        "tags": [
          "announcements"
        ],
        "security": [
          {
            "apiKey": [
              "announcements:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The announcement id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of fields to return. `id` is always included. Use it to avoid pulling personal data you do not need.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "title",
                  "body",
                  "status",
                  "kind",
                  "language",
                  "audience",
                  "community",
                  "section",
                  "author",
                  "notify",
                  "comments_enabled",
                  "pinned",
                  "acknowledgements",
                  "reads",
                  "published_at",
                  "created_at",
                  "updated_at",
                  "url"
                ]
              }
            },
            "example": [
              "external_id",
              "department",
              "status"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "body": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "language": {
                      "type": "string"
                    },
                    "audience": {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string"
                        },
                        "ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "names": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "community": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    },
                    "section": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    },
                    "author": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "email": {
                          "type": "string"
                        },
                        "external_id": {
                          "type": "string"
                        }
                      }
                    },
                    "notify": {
                      "type": "boolean"
                    },
                    "comments_enabled": {
                      "type": "boolean"
                    },
                    "pinned": {
                      "type": "boolean"
                    },
                    "acknowledgements": {
                      "type": "object",
                      "properties": {
                        "required": {
                          "type": "boolean"
                        },
                        "confirmed": {
                          "type": "integer"
                        }
                      }
                    },
                    "reads": {
                      "type": "integer"
                    },
                    "published_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "url": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/announcements/{id}/acknowledgements": {
      "get": {
        "operationId": "listAcknowledgements",
        "summary": "Who confirmed a required reading",
        "description": "Named, one row per person, with who has confirmed and who has not. This is the one nominal list in the whole API, and it is nominal because that is the entire point of required reading: a signed, timed, voluntary act you can show an inspector. Analytics stays aggregated over in /metrics for exactly the opposite reason. Only works on announcements marked as required reading.",
        "tags": [
          "announcements"
        ],
        "security": [
          {
            "apiKey": [
              "announcements:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The announcement id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page to return. The first page is 1, not 0.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 2
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "example": 100
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Everyone, only those who confirmed, or only those still pending.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "acknowledged",
                "pending"
              ],
              "default": "all"
            }
          },
          {
            "name": "department",
            "in": "query",
            "required": false,
            "description": "Only people in these departments, by name.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "center",
            "in": "query",
            "required": false,
            "description": "Only people at these sites, by name.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Field to sort by. Defaults to name.",
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "acknowledged_at"
              ],
              "default": "name"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "announcement": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "title": {
                          "type": "string"
                        }
                      }
                    },
                    "summary": {
                      "type": "object",
                      "properties": {
                        "recipients": {
                          "type": "integer"
                        },
                        "acknowledged": {
                          "type": "integer"
                        },
                        "pending": {
                          "type": "integer"
                        }
                      }
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "person": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "external_id": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "job_title": {
                                "type": "string"
                              },
                              "department": {
                                "type": "string"
                              },
                              "center": {
                                "type": "string"
                              },
                              "role": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "language": {
                                "type": "string"
                              },
                              "created_at": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "updated_at": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          },
                          "acknowledged": {
                            "type": "boolean"
                          },
                          "acknowledged_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/metrics": {
      "get": {
        "operationId": "getMetrics",
        "summary": "Company figures",
        "description": "The numbers for your own dashboard. Every figure comes from the same queries as our analytics screen, on purpose: two similar queries in two places do not give two similar numbers, they give two different ones, and the argument that follows always ends with both being dropped. Nobody is named here — reach is aggregated by design.",
        "tags": [
          "metrics"
        ],
        "security": [
          {
            "apiKey": [
              "metrics:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "period_days",
            "in": "query",
            "required": false,
            "description": "How many days back the period covers. Applies to every figure with a period, including the reach lists.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 30
            },
            "example": 90
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "Which blocks to compute, comma-separated. Leave it out for all of them. `reach_by_announcement` is the expensive one: skip it if your dashboard only draws the headline figures.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "workforce",
                  "announcements",
                  "reach_by_announcement",
                  "reach_by_department"
                ]
              }
            },
            "example": [
              "workforce",
              "announcements"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhook endpoints",
        "description": "Your registered endpoints, each with its health: consecutive failures, what your server last answered and, if we paused it, why. The signing secret is never returned — it is shown once, when the endpoint is created or the secret is rotated.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": [
              "webhooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page to return. The first page is 1, not 0.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 2
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "example": 100
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Only endpoints subscribed to this event.",
            "schema": {
              "type": "string",
              "enum": [
                "announcement.published",
                "announcement.pending_review",
                "announcement.acknowledged",
                "person.created",
                "person.deactivated"
              ]
            }
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "Only live endpoints, or only the switched-off and paused.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "events": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "active": {
                            "type": "boolean"
                          },
                          "health": {
                            "type": "object",
                            "properties": {
                              "consecutive_failures": {
                                "type": "integer"
                              },
                              "last_attempt_at": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "last_status": {
                                "type": "integer"
                              },
                              "last_error": {
                                "type": "string"
                              },
                              "paused_at": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "paused_reason": {
                                "type": "string"
                              }
                            }
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total number of items across all pages."
                    }
                  },
                  "required": [
                    "data",
                    "page",
                    "per_page",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Register a webhook endpoint",
        "description": "Tell us where to call and what to call about. THE REPLY CARRIES THE SIGNING SECRET AND IT IS THE ONLY TIME YOU WILL SEE IT. Every delivery is signed with it: verify the signature or anyone who finds your URL can post whatever they like to it. The address must be https and must not resolve to a private network — we check that when you register it and again before every single delivery. A company can have up to 20 endpoints; fan out on your side from a single delivery rather than registering dozens. The reply also spells out how to verify the signature, because that is the moment you are writing that code.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": [
              "webhooks:write"
            ]
          }
        ],
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "description": "Where we POST. https only, no credentials, no fragment.",
                    "type": "string",
                    "maxLength": 500
                  },
                  "events": {
                    "description": "Which events this endpoint wants. At least one.",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "announcement.published",
                        "announcement.pending_review",
                        "announcement.acknowledged",
                        "person.created",
                        "person.deactivated"
                      ]
                    }
                  },
                  "name": {
                    "description": "What it is for, in your words. It is the only thing that lets you delete the right one six months from now.",
                    "type": "string",
                    "maxLength": 120
                  },
                  "secret": {
                    "description": "Your own signing secret, if you would rather set it than have one generated. At least 32 characters.",
                    "type": "string",
                    "maxLength": 200
                  },
                  "active": {
                    "description": "Start delivering straight away.",
                    "type": "boolean",
                    "default": true
                  }
                },
                "required": [
                  "url",
                  "events"
                ],
                "additionalProperties": false
              },
              "example": {
                "url": "https://hooks.example.com/blastack",
                "events": [
                  "announcement.published"
                ],
                "name": "Zapier · switchboard"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "health": {
                      "type": "object",
                      "properties": {
                        "consecutive_failures": {
                          "type": "integer"
                        },
                        "last_attempt_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "last_status": {
                          "type": "integer"
                        },
                        "last_error": {
                          "type": "string"
                        },
                        "paused_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "paused_reason": {
                          "type": "string"
                        }
                      }
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  },
                  "description": "Also carries `secret`, `secret_notice` and a `signature` block. The secret is shown here and nowhere else, ever."
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It clashes with something that already exists. The reply carries the id of what it clashed with.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}": {
      "get": {
        "operationId": "getWebhook",
        "summary": "Get one webhook endpoint",
        "description": "The same shape as one item of the list, health included.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": [
              "webhooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "health": {
                      "type": "object",
                      "properties": {
                        "consecutive_failures": {
                          "type": "integer"
                        },
                        "last_attempt_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "last_status": {
                          "type": "integer"
                        },
                        "last_error": {
                          "type": "string"
                        },
                        "paused_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "paused_reason": {
                          "type": "string"
                        }
                      }
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateWebhook",
        "summary": "Update a webhook endpoint",
        "description": "Changes only what you send. Setting `active` to true also clears a pause we applied after repeated failures and resets the failure count — that is how you bring an endpoint back after fixing your server. `rotate_secret` returns a new secret once and the very next delivery is signed with it, so change it on your side first.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": [
              "webhooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "description": "Where we POST. https only.",
                    "type": "string",
                    "maxLength": 500
                  },
                  "events": {
                    "description": "Which events this endpoint wants. At least one.",
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "announcement.published",
                        "announcement.pending_review",
                        "announcement.acknowledged",
                        "person.created",
                        "person.deactivated"
                      ]
                    }
                  },
                  "name": {
                    "description": "What it is for.",
                    "type": "string",
                    "maxLength": 120
                  },
                  "active": {
                    "description": "Switch delivery on or off. Turning it on clears a pause.",
                    "type": "boolean"
                  },
                  "rotate_secret": {
                    "description": "Generate a new signing secret and return it once in this reply.",
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "health": {
                      "type": "object",
                      "properties": {
                        "consecutive_failures": {
                          "type": "integer"
                        },
                        "last_attempt_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "last_status": {
                          "type": "integer"
                        },
                        "last_error": {
                          "type": "string"
                        },
                        "paused_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "paused_reason": {
                          "type": "string"
                        }
                      }
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "It clashes with something that already exists. The reply carries the id of what it clashed with.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook endpoint",
        "description": "Removes the endpoint and its delivery history. Anything still queued for it is dropped. If you only want to stop deliveries for a while, PATCH `active` to false instead: that keeps the history.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": [
              "webhooks:write"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "active": {
                      "type": "boolean"
                    },
                    "health": {
                      "type": "object",
                      "properties": {
                        "consecutive_failures": {
                          "type": "integer"
                        },
                        "last_attempt_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "last_status": {
                          "type": "integer"
                        },
                        "last_error": {
                          "type": "string"
                        },
                        "paused_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "paused_reason": {
                          "type": "string"
                        }
                      }
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "updated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "listDeliveries",
        "summary": "Delivery log of a webhook endpoint",
        "description": "Every event we tried to send you: how many attempts, what your server answered, when the next attempt is due. The `id` here is the same one that travelled in the Blastack-Event-Id header, so you can look up anything you see in your own logs. This is the endpoint that answers 'why is nothing arriving' without opening a ticket.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "apiKey": [
              "webhooks:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The webhook endpoint id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page to return. The first page is 1, not 0.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 2
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "description": "How many items per page. Anything above 200 is clamped to 200 rather than rejected: asking for more is a reasonable thing to want.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "example": 100
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only deliveries in this state.",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivered",
                "given_up"
              ]
            }
          },
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Only deliveries of this event type.",
            "schema": {
              "type": "string",
              "enum": [
                "announcement.published",
                "announcement.pending_review",
                "announcement.acknowledged",
                "person.created",
                "person.deactivated"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only events that happened at or after this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "event": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "attempts": {
                            "type": "integer"
                          },
                          "occurred_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "delivered_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "given_up_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "next_attempt_at": {},
                          "last_status": {
                            "type": "integer"
                          },
                          "last_error": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "page": {
                      "type": "integer"
                    },
                    "per_page": {
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer",
                      "description": "Total number of items across all pages."
                    }
                  },
                  "required": [
                    "data",
                    "page",
                    "per_page",
                    "total"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Something in the request does not fit. The message names the exact parameter or field, and lists the values it accepts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed or revoked API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The key lacks the scope, the company has not enabled the corporate identity module, or the free trial ended and the company is read-only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Nothing of yours with that id. Also what you get for something that belongs to another company: telling the two apart would be a way of finding out what exists elsewhere.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 120 requests in a minute. Retry-After says how long to wait.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our side.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "announcement.published": {
      "post": {
        "operationId": "onannouncementPublished",
        "summary": "announcement.published",
        "description": "Something went live on the wall. Fires for every route it can arrive by — the composer, the full editor, this API, a post released from the review queue and a scheduled one reaching its hour — and for required-reading posts too. `data.announcement.kind` says what it is: `news` is the usual one, and `survey`, `event`, `album`, `document` and `recognition` are the wall entries the other modules create. Filter on it if you only want plain announcements.\n\nWe POST this to every endpoint of the company subscribed to it, within a minute. Headers: `blastack-event` (the type), `blastack-event-id` (the event id, stable across retries — key on it to drop duplicates), `blastack-delivery-attempt` (which attempt this is) and `blastack-signature`, which is `t=<unix seconds>,v1=<hex>` where v1 is HMAC-SHA256 of `<t>.<raw body>` with your signing secret. Compare it in constant time and reject anything whose `t` is more than a few minutes old, or an old delivery can be replayed at you forever with a signature that still verifies. Answer with any 2xx; anything else is retried six times with growing waits over about ten hours.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "announcement": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "body": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "language": {
                            "type": "string"
                          },
                          "requires_acknowledgement": {
                            "type": "boolean"
                          },
                          "audience": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string"
                              },
                              "names": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            }
                          },
                          "community": {
                            "type": "string"
                          },
                          "author": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string"
                              },
                              "external_id": {
                                "type": "string"
                              }
                            }
                          },
                          "url": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                "type": "announcement.published",
                "occurred_at": "2026-09-01T06:00:00.000Z",
                "organization_id": "0a9b8c7d-6e5f-4a3b-8c9d-0e1f2a3b4c5d",
                "data": {
                  "announcement": {
                    "id": "6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
                    "title": "Parada de la línea 3 el jueves",
                    "body": "El jueves 4 a las 06:00 para la línea 3 por mantenimiento programado.",
                    "kind": "news",
                    "language": "es",
                    "requires_acknowledgement": true,
                    "audience": {
                      "type": "departments",
                      "names": [
                        "Mantenimiento",
                        "Calidad"
                      ]
                    },
                    "community": "Planta de Getafe",
                    "author": {
                      "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                      "name": "Ana García",
                      "email": "ana.garcia@empresa.com",
                      "external_id": "EMP-0042"
                    },
                    "url": "https://app.blastack.com/inicio#post-6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
                    "status": "published"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx means you took it. We do not read the body, so answer fast and do the work afterwards: we give up after 10 seconds and that counts as a failure."
          }
        }
      }
    },
    "announcement.pending_review": {
      "post": {
        "operationId": "onannouncementPending_review",
        "summary": "announcement.pending_review",
        "description": "An announcement is waiting for approval before it can be published. Only fires in companies that have the review queue on. It fires when the post enters the queue, whether it was written just now or was a draft submitted later.\n\nWe POST this to every endpoint of the company subscribed to it, within a minute. Headers: `blastack-event` (the type), `blastack-event-id` (the event id, stable across retries — key on it to drop duplicates), `blastack-delivery-attempt` (which attempt this is) and `blastack-signature`, which is `t=<unix seconds>,v1=<hex>` where v1 is HMAC-SHA256 of `<t>.<raw body>` with your signing secret. Compare it in constant time and reject anything whose `t` is more than a few minutes old, or an old delivery can be replayed at you forever with a signature that still verifies. Answer with any 2xx; anything else is retried six times with growing waits over about ten hours.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "announcement": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "body": {
                            "type": "string"
                          },
                          "kind": {
                            "type": "string"
                          },
                          "language": {
                            "type": "string"
                          },
                          "requires_acknowledgement": {
                            "type": "boolean"
                          },
                          "audience": {
                            "type": "object",
                            "properties": {
                              "type": {
                                "type": "string"
                              },
                              "names": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            }
                          },
                          "community": {
                            "type": "string"
                          },
                          "author": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string"
                              },
                              "external_id": {
                                "type": "string"
                              }
                            }
                          },
                          "url": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                "type": "announcement.pending_review",
                "occurred_at": "2026-09-01T06:00:00.000Z",
                "organization_id": "0a9b8c7d-6e5f-4a3b-8c9d-0e1f2a3b4c5d",
                "data": {
                  "announcement": {
                    "id": "6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
                    "title": "Parada de la línea 3 el jueves",
                    "body": "El jueves 4 a las 06:00 para la línea 3 por mantenimiento programado.",
                    "kind": "news",
                    "language": "es",
                    "requires_acknowledgement": true,
                    "audience": {
                      "type": "departments",
                      "names": [
                        "Mantenimiento",
                        "Calidad"
                      ]
                    },
                    "community": "Planta de Getafe",
                    "author": {
                      "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                      "name": "Ana García",
                      "email": "ana.garcia@empresa.com",
                      "external_id": "EMP-0042"
                    },
                    "url": "https://app.blastack.com/inicio#post-6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
                    "status": "pending_review"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx means you took it. We do not read the body, so answer fast and do the work afterwards: we give up after 10 seconds and that counts as a failure."
          }
        }
      }
    },
    "announcement.acknowledged": {
      "post": {
        "operationId": "onannouncementAcknowledged",
        "summary": "announcement.acknowledged",
        "description": "A person confirmed they have read a required-reading announcement. One event per person, carrying who and when.\n\nWe POST this to every endpoint of the company subscribed to it, within a minute. Headers: `blastack-event` (the type), `blastack-event-id` (the event id, stable across retries — key on it to drop duplicates), `blastack-delivery-attempt` (which attempt this is) and `blastack-signature`, which is `t=<unix seconds>,v1=<hex>` where v1 is HMAC-SHA256 of `<t>.<raw body>` with your signing secret. Compare it in constant time and reject anything whose `t` is more than a few minutes old, or an old delivery can be replayed at you forever with a signature that still verifies. Answer with any 2xx; anything else is retried six times with growing waits over about ten hours.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "announcement": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          }
                        }
                      },
                      "person": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string"
                          },
                          "external_id": {
                            "type": "string"
                          },
                          "department": {
                            "type": "string"
                          },
                          "center": {
                            "type": "string"
                          }
                        }
                      },
                      "acknowledged_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              },
              "example": {
                "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                "type": "announcement.acknowledged",
                "occurred_at": "2026-09-01T06:00:00.000Z",
                "organization_id": "0a9b8c7d-6e5f-4a3b-8c9d-0e1f2a3b4c5d",
                "data": {
                  "announcement": {
                    "id": "6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40",
                    "title": "Parada de la línea 3 el jueves",
                    "url": "https://app.blastack.com/inicio#post-6b1f0f1e-0c2a-4c5e-9a3d-2f5b8c7d1e40"
                  },
                  "person": {
                    "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                    "name": "Ana García",
                    "email": "ana.garcia@empresa.com",
                    "external_id": "EMP-0042",
                    "department": "Mantenimiento",
                    "center": "Nave 2"
                  },
                  "acknowledged_at": "2026-09-01T07:12:44.000Z"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx means you took it. We do not read the body, so answer fast and do the work afterwards: we give up after 10 seconds and that counts as a failure."
          }
        }
      }
    },
    "person.created": {
      "post": {
        "operationId": "onpersonCreated",
        "summary": "person.created",
        "description": "A person was added to the company, no matter how: SCIM, an invite code, single sign-on, the admin screen or this API.\n\nWe POST this to every endpoint of the company subscribed to it, within a minute. Headers: `blastack-event` (the type), `blastack-event-id` (the event id, stable across retries — key on it to drop duplicates), `blastack-delivery-attempt` (which attempt this is) and `blastack-signature`, which is `t=<unix seconds>,v1=<hex>` where v1 is HMAC-SHA256 of `<t>.<raw body>` with your signing secret. Compare it in constant time and reject anything whose `t` is more than a few minutes old, or an old delivery can be replayed at you forever with a signature that still verifies. Answer with any 2xx; anything else is retried six times with growing waits over about ten hours.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "person": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string"
                          },
                          "external_id": {
                            "type": "string"
                          },
                          "job_title": {
                            "type": "string"
                          },
                          "department": {
                            "type": "string"
                          },
                          "center": {
                            "type": "string"
                          },
                          "role": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "source": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                "type": "person.created",
                "occurred_at": "2026-09-01T06:00:00.000Z",
                "organization_id": "0a9b8c7d-6e5f-4a3b-8c9d-0e1f2a3b4c5d",
                "data": {
                  "person": {
                    "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                    "name": "Ana García",
                    "email": "ana.garcia@empresa.com",
                    "external_id": "EMP-0042",
                    "job_title": "Jefa de turno",
                    "department": "Mantenimiento",
                    "center": "Nave 2",
                    "role": "editor",
                    "status": "active",
                    "source": "api"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx means you took it. We do not read the body, so answer fast and do the work afterwards: we give up after 10 seconds and that counts as a failure."
          }
        }
      }
    },
    "person.deactivated": {
      "post": {
        "operationId": "onpersonDeactivated",
        "summary": "person.deactivated",
        "description": "A person was deactivated. Blastack never deletes people, so this is the offboarding signal.\n\nWe POST this to every endpoint of the company subscribed to it, within a minute. Headers: `blastack-event` (the type), `blastack-event-id` (the event id, stable across retries — key on it to drop duplicates), `blastack-delivery-attempt` (which attempt this is) and `blastack-signature`, which is `t=<unix seconds>,v1=<hex>` where v1 is HMAC-SHA256 of `<t>.<raw body>` with your signing secret. Compare it in constant time and reject anything whose `t` is more than a few minutes old, or an old delivery can be replayed at you forever with a signature that still verifies. Answer with any 2xx; anything else is retried six times with growing waits over about ten hours.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "occurred_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "person": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "email": {
                            "type": "string"
                          },
                          "external_id": {
                            "type": "string"
                          },
                          "job_title": {
                            "type": "string"
                          },
                          "department": {
                            "type": "string"
                          },
                          "center": {
                            "type": "string"
                          },
                          "role": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "source": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
                "type": "person.deactivated",
                "occurred_at": "2026-09-01T06:00:00.000Z",
                "organization_id": "0a9b8c7d-6e5f-4a3b-8c9d-0e1f2a3b4c5d",
                "data": {
                  "person": {
                    "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                    "name": "Ana García",
                    "email": "ana.garcia@empresa.com",
                    "external_id": "EMP-0042",
                    "job_title": "Jefa de turno",
                    "department": "Mantenimiento",
                    "center": "Nave 2",
                    "role": "editor",
                    "status": "disabled",
                    "source": "api"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx means you took it. We do not read the body, so answer fast and do the work afterwards: we give up after 10 seconds and that counts as a failure."
          }
        }
      }
    }
  }
}