Granite Webhooks API

Signed HTTP callbacks for vault events, and integrations built on them. Granite posts a JSON `WebhookEvent` to your URL within seconds of each change. Integrations connect a service to one vault so it can act on notes and folders. Chat apps (`slack`, `teams`, `mattermost`, `discord`, `telegram`, `rocket_chat`, `zulip`) take commands; automation tools (`n8n`, `pipedream`, `zapier`, `make`, `ifttt`, `activepieces`, `power_automate`, `webhook`) and `shortcuts` take actions. Automation tools can, optionally, start a workflow when a note is added, moved, or deleted (`note.created`, `note.moved`, `note.deleted`): the signed `WebhookEvent` JSON described below, posted to their `url`. Edits never start an integration's workflow; only a custom `webhook` may subscribe to `note.updated` or `*`. Chat apps get no events. Which kinds a plan may use, and how many per vault, is in `GET /v1/me/entitlements` (`integrations`, `integrations_per_vault`); custom webhooks need a paid plan. An integration's addresses and secrets are stored encrypted and never shown again. Commands and actions go to the integration's `inbound_url` (`POST /v1/integrations/{id}/inbound`) and run as the person who installed it, in its vault only, limited to its `actions`. Chat commands: `new ` (a line break or ` | ` starts the text), `append | `, `daily `, `search `, `list `, `read `, `replace | `, `move | `, `delete ` (a folder with a trailing `/`), and plain text, which is captured to `/Inbox.md`. Each service proves itself its own way: Slack's signing secret, Discord's Ed25519 key, Telegram's secret token (and only the connected chat), Teams' HMAC, or the token of a Mattermost, Rocket.Chat, or Zulip webhook. Automation tools send JSON with the `action_key` as `Authorization: Bearer …` (or `?key=`): `{"action": "create", "title": "…", "content": "…", "folder": "…"}`, and likewise `append`, `daily`, `capture`, `search` (`query`), `list` (`folder`), `read`, `update`, `move` (`path` or `folder`, and `to`), `delete` (`path` or `folder`). A change made this way is not sent back to the same integration as a trigger. Until its secrets are saved (`ready`), an integration answers nothing. AI meeting notetakers send each finished meeting to `inbound_url` with their own webhook, and Granite saves it as a note in the integration's `folder` (default `Meetings`): summary, action items as tasks, attendees, a link to the recording, and the transcript in a folded callout; YAML frontmatter has `type: meeting`, `source`, `date`, `attendees`, and `meeting_id`. The same meeting sent again replaces its own note. Requests are checked with the service's signing secret (`signing_secret`): Read.ai's `X-Read-Signature`, Fathom's and Granola's Standard Webhooks signature (`whsec_…`), Fireflies' `X-Hub-Signature`, Circleback's `X-Signature`, Avoma's `X-Avoma-Signature`, or MeetGeek's `X-MG-Signature`. tl;dv sends the `action_key` as a header, Otter as `?key=`. Fireflies, Granola, and MeetGeek send only the meeting's ID; Granite fetches it with the API key saved as `inbound_token`. Answer with any 2xx status within 10 seconds; other answers and timeouts are retried after 1 min, 5 min, 30 min, 2 h, 6 h, and 24 h, then the delivery is marked failed (`410 Gone` stops retries at once). Every attempt is in the delivery log and any delivery can be replayed. Delivery is at least once: skip event `id`s you have already handled. Events: `note.created`, `note.updated`, `note.moved`, `note.deleted`; `vault.shared` and `member.added`/`member.removed`/`member.role_changed` start with sharing. Subscribe with exact names, `note.*`, `member.*`, or `*`. Headers: `Granite-Event`, `Granite-Delivery`, `Granite-Webhook`, and `Granite-Signature: t=,v1=`, where the signature is the hex HMAC-SHA256 of `.` keyed with the webhook secret (`whsec_...`). Check it against the raw body before parsing, and reject timestamps more than 5 minutes old. Node.js: ```js import { createHmac, timingSafeEqual } from "node:crypto"; function verify(secret, header, rawBody) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; const want = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"); return timingSafeEqual(Buffer.from(want), Buffer.from(parts.v1 ?? "")); } ``` Python: ```python import hashlib, hmac, time def verify(secret: str, header: str, raw_body: bytes) -> bool: parts = dict(p.split("=", 1) for p in header.split(",")) if abs(time.time() - int(parts["t"])) > 300: return False want = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(want, parts.get("v1", "")) ``` Go: ```go func verify(secret, header string, body []byte) bool { var t, sig string for _, p := range strings.Split(header, ",") { k, v, _ := strings.Cut(p, "=") if k == "t" { t = v } else if k == "v1" { sig = v } } ts, err := strconv.ParseInt(t, 10, 64) if err != nil || math.Abs(time.Since(time.Unix(ts, 0)).Seconds()) > 300 { return false } m := hmac.New(sha256.New, []byte(secret)) m.Write([]byte(t + ".")) m.Write(body) return hmac.Equal([]byte(hex.EncodeToString(m.Sum(nil))), []byte(sig)) } ```

GitHub OpenAPI

Operations 10

GET /v1/vaults/{vault_id}/webhooks List a vault's webhooks #
POST /v1/vaults/{vault_id}/webhooks Add a webhook or integration #
GET /v1/vaults/{vault_id}/webhooks/{webhook_id} Get a webhook #
PATCH /v1/vaults/{vault_id}/webhooks/{webhook_id} Change a webhook #
DELETE /v1/vaults/{vault_id}/webhooks/{webhook_id} Delete a webhook #
POST /v1/vaults/{vault_id}/webhooks/{webhook_id}/connect Connect a chat to Granite's bot #
DELETE /v1/vaults/{vault_id}/webhooks/{webhook_id}/chats/{chat_id} Disconnect a chat #
POST /v1/vaults/{vault_id}/webhooks/{webhook_id}/test Send a test event #
GET /v1/vaults/{vault_id}/webhooks/{webhook_id}/deliveries The delivery log #
POST /v1/vaults/{vault_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replay Send a delivery again #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/granite-md:granite-md-webhooks-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

granite-md-webhooks-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Granite Webhooks API
  version: 1.0.0
  summary: REST API for Granite clients, bots, and integrations.
  description: 'Conventions (apply to every operation; see PLAN.md 3.2 and 3.6):


    - **Base URL** `https://api.granite.md`. All public operations live under `/v1`.

    After Phase 6 ships, `/v1` changes are additive only.

    - **Errors** use RFC 9457 problem details (`application/problem+json`, schema `Problem`).

    The `type` URI is `https://granite.md/problems/`; `code` refines it for programmatic handling.

    Every response carries `X-Request-ID`, also echoed as `request_id` in problems.

    - **Pagination** is cursor based. List operations accept `cursor` and `limit` and return

    `{ "items": [...], "next_cursor": "..." }`. `next_cursor` is absent on the last page.

    Cursors are opaque; clients must not parse or build them.

    - **Idempotency**: non-idempotent `POST` operations accept an `Idempotency-Key` header

    (1-255 printable ASCII). Repeating a request with the same key and body within 24 hours

    returns the original response; the same key with a different body returns `409` with

    code `idempotency_key_reused`.

    - **IDs** are UUIDv7 strings. **Timestamps** are RFC 3339 in UTC.

    - **Authentication**: `Authorization: Bearer ` with a session access token,

    a personal API token (`gr_pat_...`), or an OAuth access token. Scopes are listed per operation.

    - **Rate limits**: each personal API token may make 600 requests a minute. Over the limit

    the API answers `429` with code `token_rate_limited` and a `Retry-After` header (seconds).'
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
- url: https://api.granite.md
  description: Production
- url: http://localhost:8080
  description: Local development
security:
- bearerAuth: []
tags:
- name: Webhooks
  description: 'Signed HTTP callbacks for vault events, and integrations built on them. Granite posts a

    JSON `WebhookEvent` to your URL within seconds of each change. Integrations connect a service to one vault so it can act on notes and folders. Chat

    apps (`slack`, `teams`, `mattermost`, `discord`, `telegram`, `rocket_chat`, `zulip`)

    take commands; automation tools (`n8n`, `pipedream`, `zapier`, `make`, `ifttt`,

    `activepieces`, `power_automate`, `webhook`) and `shortcuts` take actions. Automation

    tools can, optionally, start a workflow when a note is added, moved, or deleted

    (`note.created`, `note.moved`, `note.deleted`): the signed `WebhookEvent` JSON described

    below, posted to their `url`. Edits never start an integration''s workflow; only a custom

    `webhook` may subscribe to `note.updated` or `*`. Chat apps get no events. Which kinds a plan may use, and

    how many per vault, is in `GET /v1/me/entitlements` (`integrations`,

    `integrations_per_vault`); custom webhooks need a paid plan. An integration''s

    addresses and secrets are stored encrypted and never shown again. Commands and actions go to the integration''s `inbound_url`

    (`POST /v1/integrations/{id}/inbound`) and run as the person who installed it, in its

    vault only, limited to its `actions`. Chat commands: `new ` (a line break

    or ` | ` starts the text), `append | `, `daily `, `search `,

    `list `, `read `, `replace | `, `move |

    `, `delete ` (a folder with a trailing `/`), and plain text,

    which is captured to `/Inbox.md`. Each service proves itself its own way:

    Slack''s signing secret, Discord''s Ed25519 key, Telegram''s secret token (and only the

    connected chat), Teams'' HMAC, or the token of a Mattermost, Rocket.Chat, or Zulip

    webhook. Automation tools send JSON with the `action_key` as `Authorization: Bearer …`

    (or `?key=`): `{"action": "create", "title": "…", "content": "…", "folder": "…"}`, and

    likewise `append`, `daily`, `capture`, `search` (`query`), `list` (`folder`), `read`,

    `update`, `move` (`path` or `folder`, and `to`), `delete` (`path` or `folder`). A change

    made this way is not sent back to the same integration as a trigger. Until its secrets

    are saved (`ready`), an integration answers nothing. AI meeting notetakers send each finished meeting to `inbound_url` with their own

    webhook, and Granite saves it as a note in the integration''s `folder` (default

    `Meetings`): summary, action items as tasks, attendees, a link to the recording, and the

    transcript in a folded callout; YAML frontmatter has `type: meeting`, `source`, `date`,

    `attendees`, and `meeting_id`. The same meeting sent again replaces its own note. Requests are checked with the service''s signing secret (`signing_secret`): Read.ai''s

    `X-Read-Signature`, Fathom''s and Granola''s Standard Webhooks signature (`whsec_…`),

    Fireflies'' `X-Hub-Signature`, Circleback''s `X-Signature`, Avoma''s `X-Avoma-Signature`,

    or MeetGeek''s `X-MG-Signature`. tl;dv sends the `action_key` as a header, Otter as

    `?key=`. Fireflies, Granola, and MeetGeek send only the meeting''s ID; Granite fetches it

    with the API key saved as `inbound_token`. Answer with any 2xx status within 10 seconds; other answers and timeouts are retried after 1 min, 5 min, 30 min,

    2 h, 6 h, and 24 h, then the delivery is marked failed (`410 Gone` stops retries at

    once). Every attempt is in the delivery log and any delivery can be replayed. Delivery

    is at least once: skip event `id`s you have already handled. Events: `note.created`, `note.updated`, `note.moved`, `note.deleted`; `vault.shared`

    and `member.added`/`member.removed`/`member.role_changed` start with sharing. Subscribe with exact names, `note.*`, `member.*`, or `*`. Headers: `Granite-Event`, `Granite-Delivery`, `Granite-Webhook`, and

    `Granite-Signature: t=,v1=`, where the signature is the hex

    HMAC-SHA256 of `.` keyed with the webhook secret (`whsec_...`). Check it

    against the raw body before parsing, and reject timestamps more than 5 minutes old.'
paths:
  /v1/vaults/{vault_id}/webhooks:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    get:
      operationId: listWebhooks
      tags:
      - Webhooks
      summary: List a vault's webhooks
      description: Needs administrator access to the vault.
      responses:
        '200':
          description: Webhooks.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookList'
        default:
          $ref: '#/components/responses/Error'
    post:
      operationId: createWebhook
      tags:
      - Webhooks
      summary: Add a webhook or integration
      description: 'The kind must be in the plan (403 `plan_limit_integration`, or `plan_limit_webhooks`

        for custom webhooks), and the vault under the plan''s `integrations_per_vault` (403

        `too_many_webhooks`). Events from now on are delivered. The secret is returned only

        here and when rotated.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookCreate'
      responses:
        '201':
          description: The webhook and its secret.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookWithSecret'
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/webhooks/{webhook_id}:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    - $ref: '#/components/parameters/WebhookID'
    get:
      operationId: getWebhook
      tags:
      - Webhooks
      summary: Get a webhook
      responses:
        '200':
          description: The webhook.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webhook'
        default:
          $ref: '#/components/responses/Error'
    patch:
      operationId: updateWebhook
      tags:
      - Webhooks
      summary: Change a webhook
      description: Omitted fields keep their value. `rotate_secret` returns a new secret once.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookUpdate'
      responses:
        '200':
          description: The webhook (with `secret` when it was rotated).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookWithSecret'
        default:
          $ref: '#/components/responses/Error'
    delete:
      operationId: deleteWebhook
      tags:
      - Webhooks
      summary: Delete a webhook
      description: Its pending deliveries and delivery log are deleted too.
      responses:
        '204':
          description: Deleted.
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/webhooks/{webhook_id}/connect:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    - $ref: '#/components/parameters/WebhookID'
    post:
      operationId: connectIntegration
      tags:
      - Webhooks
      summary: Connect a chat to Granite's bot
      description: 'For integrations on Granite''s own bot (`granite_bot`): a one-time code, valid for 30

        minutes, and the link that carries it (Telegram''s start link, Slack''s or Discord''s

        install page). The chat it is used in, or where `link CODE` is typed, is connected.'
      responses:
        '201':
          description: The code and link.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntegrationConnect'
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/webhooks/{webhook_id}/chats/{chat_id}:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    - $ref: '#/components/parameters/WebhookID'
    - name: chat_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    delete:
      operationId: disconnectIntegrationChat
      tags:
      - Webhooks
      summary: Disconnect a chat
      description: Commands from the chat no longer reach Granite.
      responses:
        '204':
          description: Disconnected.
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/webhooks/{webhook_id}/test:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    - $ref: '#/components/parameters/WebhookID'
    post:
      operationId: testWebhook
      tags:
      - Webhooks
      summary: Send a test event
      description: Queues a `ping` event, sent right away, so you can check the connection.
      responses:
        '202':
          description: The new delivery.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDelivery'
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/webhooks/{webhook_id}/deliveries:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    - $ref: '#/components/parameters/WebhookID'
    get:
      operationId: listWebhookDeliveries
      tags:
      - Webhooks
      summary: The delivery log
      description: Deliveries, newest first, with the outcome of their latest attempt.
      parameters:
      - $ref: '#/components/parameters/Cursor'
      - $ref: '#/components/parameters/Limit'
      responses:
        '200':
          description: Deliveries.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDeliveryList'
        default:
          $ref: '#/components/responses/Error'
  /v1/vaults/{vault_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/replay:
    parameters:
    - $ref: '#/components/parameters/VaultID'
    - $ref: '#/components/parameters/WebhookID'
    - name: delivery_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    post:
      operationId: replayWebhookDelivery
      tags:
      - Webhooks
      summary: Send a delivery again
      description: Creates a new delivery of the same event (same event `id`), sent right away.
      responses:
        '202':
          description: The new delivery.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDelivery'
        default:
          $ref: '#/components/responses/Error'
components:
  schemas:
    WebhookList:
      type: object
      required:
      - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Webhook'
    IntegrationConnect:
      type: object
      required:
      - code
      - url
      - expires_at
      properties:
        code:
          type: string
          description: One-time code (`ABCDE-12345`); typed in a chat as `link CODE`.
        url:
          type: string
          description: Opens the service with the code (Telegram) or adds Granite to it (Slack, Discord).
        group_url:
          type: string
          description: Telegram only, adds the bot to a group with the code.
        expires_at:
          type: string
          format: date-time
    WebhookWithSecret:
      type: object
      required:
      - webhook
      properties:
        webhook:
          $ref: '#/components/schemas/Webhook'
        secret:
          type: string
          description: The signing secret (`whsec_...`). Shown only when created or rotated.
        action_key:
          type: string
          description: 'Automation tools: the key for `inbound_url` (`gik_...`). Shown only when actions

            are first turned on or the key is rotated.

            '
    FieldError:
      type: object
      required:
      - field
      - detail
      properties:
        field:
          type: string
          description: JSON path of the invalid field, for example `ops[2].path`.
        detail:
          type: string
    IntegrationKind:
      type: string
      description: 'The connected service. `webhook` is a custom endpoint. AI meeting notetakers

        (`read_ai`, `fathom`, `fireflies`, `tldv`, `otter`, `granola`, `circleback`, `avoma`,

        `meetgeek`) send finished meetings. `google_chat`, `webex`, and `ntfy` could only notify

        and can no longer be added.

        '
      enum:
      - slack
      - teams
      - mattermost
      - discord
      - telegram
      - rocket_chat
      - zulip
      - shortcuts
      - n8n
      - pipedream
      - zapier
      - make
      - ifttt
      - activepieces
      - power_automate
      - webhook
      - read_ai
      - fathom
      - fireflies
      - tldv
      - otter
      - granola
      - circleback
      - avoma
      - meetgeek
      - google_chat
      - webex
      - ntfy
    Problem:
      type: object
      description: RFC 9457 problem details.
      required:
      - type
      - title
      - status
      properties:
        type:
          type: string
          format: uri
          examples:
          - https://granite.md/problems/conflict
        title:
          type: string
        status:
          type: integer
          format: int32
        detail:
          type: string
        instance:
          type: string
        code:
          type: string
          description: Machine-readable refinement of `type`, for example `version_conflict`.
        request_id:
          type: string
        errors:
          type: array
          description: Per-field validation errors.
          items:
            $ref: '#/components/schemas/FieldError'
    WebhookUpdate:
      type: object
      properties:
        url:
          type: string
          maxLength: 2000
        bot_token:
          type: string
          maxLength: 100
        chat_id:
          type: string
          maxLength: 40
        actions:
          type: array
          items:
            $ref: '#/components/schemas/IntegrationAction'
        folder:
          type: string
          maxLength: 200
        signing_secret:
          type: string
          maxLength: 200
          description: '`slack`: the app''s signing secret (Basic Information); notetakers: the webhook''s signing secret.'
        public_key:
          type: string
          maxLength: 200
          description: '`discord`: the application''s public key (hex).'
        application_id:
          type: string
          maxLength: 40
          description: '`discord`: the application ID; with `bot_token`, Granite registers the slash commands.'
        inbound_token:
          type: string
          maxLength: 200
          description: '`teams`: the outgoing webhook''s security token; `mattermost`, `rocket_chat`, `zulip`: the webhook''s token; `fireflies`, `granola`, `meetgeek`: the API key meetings are fetched with.'
        rotate_action_key:
          type: boolean
        description:
          type: string
          maxLength: 200
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        active:
          type: boolean
        rotate_secret:
          type: boolean
    WebhookCreate:
      type: object
      required:
      - events
      properties:
        kind:
          $ref: '#/components/schemas/IntegrationKind'
        url:
          type: string
          maxLength: 2000
          description: 'Automation tools: the https URL that starts the workflow (a catch hook), for

            triggers. Chat apps need none.

            '
        bot_token:
          type: string
          maxLength: 100
          description: '`telegram`: the bot''s token from @BotFather.'
        chat_id:
          type: string
          maxLength: 40
          description: '`telegram`: the chat, group, or channel (`-100…` or `@name`) the bot posts to.'
        actions:
          type: array
          items:
            $ref: '#/components/schemas/IntegrationAction'
        folder:
          type: string
          maxLength: 200
        signing_secret:
          type: string
          maxLength: 200
          description: '`slack`: the app''s signing secret (Basic Information); notetakers: the webhook''s signing secret.'
        public_key:
          type: string
          maxLength: 200
          description: '`discord`: the application''s public key (hex).'
        application_id:
          type: string
          maxLength: 40
          description: '`discord`: the application ID; with `bot_token`, Granite registers the slash commands.'
        inbound_token:
          type: string
          maxLength: 200
          description: '`teams`: the outgoing webhook''s security token; `mattermost`, `rocket_chat`, `zulip`: the webhook''s token; `fireflies`, `granola`, `meetgeek`: the API key meetings are fetched with.'
        description:
          type: string
          maxLength: 200
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
    WebhookDelivery:
      type: object
      required:
      - id
      - event_id
      - event_type
      - state
      - attempts
      - created_at
      - payload
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: string
          format: uuid
        event_type:
          type: string
        state:
          type: string
          enum:
          - pending
          - succeeded
          - failed
        attempts:
          type: integer
        next_attempt_at:
          type: string
          format: date-time
        last_status:
          type: integer
          description: The HTTP status of the latest attempt (absent if no answer).
        last_error:
          type: string
        replay_of:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        delivered_at:
          type: string
          format: date-time
        payload:
          $ref: '#/components/schemas/WebhookEvent'
    WebhookDeliveryList:
      type: object
      required:
      - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDelivery'
        next_cursor:
          type: string
    IntegrationChat:
      type: object
      required:
      - id
      - name
      - created_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          description: The chat, channel, or server, as the service named it.
        created_at:
          type: string
          format: date-time
    IntegrationAction:
      type: string
      description: 'What an integration may do: `capture` (plain messages to the inbox note), `create`,

        `append`, `daily` (today''s daily note), `search`, `list` (a folder), `read`, `update`

        (replace the text), `move` and `delete` (a note or a whole folder), and `meeting`

        (notetakers only: save each finished meeting as a note).

        '
      enum:
      - capture
      - create
      - append
      - daily
      - search
      - list
      - read
      - update
      - move
      - delete
      - meeting
    Webhook:
      type: object
      required:
      - id
      - vault_id
      - kind
      - url
      - description
      - events
      - actions
      - folder
      - ready
      - granite_bot
      - active
      - created_at
      - updated_at
      properties:
        id:
          type: string
          format: uuid
        vault_id:
          type: string
          format: uuid
        kind:
          $ref: '#/components/schemas/IntegrationKind'
        url:
          type: string
          description: 'Where triggers go: a masked copy of the tool''s URL (the real address works as a

            password); for Telegram `telegram:<chat id>`; empty for chat apps.

            '
        description:
          type: string
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        actions:
          type: array
          items:
            $ref: '#/components/schemas/IntegrationAction'
        folder:
          type: string
          description: Where new notes and the capture inbox go.
        inbound_url:
          type: string
          description: Where the service sends commands and actions.
        ready:
          type: boolean
          description: Everything requests are checked with is saved, so commands work.
        last_inbound_at:
          type: string
          format: date-time
          description: When the service last sent a verified command or action.
        granite_bot:
          type: boolean
          description: 'Telegram, Slack, and Discord: the integration uses Granite''s own bot, so there is

            nothing to set up in the service; chats are connected with `connect`.

            '
        chats:
          type: array
          description: Granite's bot only, the chats connected to the integration.
          items:
            $ref: '#/components/schemas/IntegrationChat'
        active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    WebhookEvent:
      type: object
      description: The JSON body Granite posts.
      required:
      - id
      - type
      - created_at
      - vault_id
      - data
      properties:
        id:
          type: string
          format: uuid
          description: The event ID; the same in replays and retries.
        type:
          type: string
        created_at:
          type: string
          format: date-time
        vault_id:
          type: string
          format: uuid
        data:
          type: object
          additionalProperties: true
          description: 'For note events: `file_id`, `version_id`, `path`, `old_path` (moves), `kind`

            (markdown, text, binary), `size`, `author_id`, `seq`. The text is not included;

            read it with the notes API.

            '
    WebhookEventType:
      type: string
      enum:
      - '*'
      - note.*
      - member.*
      - note.created
      - note.updated
      - note.moved
      - note.deleted
      - vault.shared
      - member.added
      - member.removed
      - member.role_changed
  headers:
    RequestID:
      description: Identifier of this request, for support and log correlation.
      schema:
        type: string
  parameters:
    VaultID:
      name: vault_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    WebhookID:
      name: webhook_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    Limit:
      name: limit
      in: query
      required: false
      description: Maximum items to return.
      schema:
        type: integer
        format: int32
        minimum: 1
        maximum: 200
        default: 50
    Cursor:
      name: cursor
      in: query
      required: false
      description: Opaque cursor from a previous page's `next_cursor`.
      schema:
        type: string
        maxLength: 512
  responses:
    Error:
      description: Error response.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Session access token, personal API token, or OAuth access token.