Skip to documentation
Docs Build a Nook bot
Browse documentation
Apps & Bots · API

Bot API cookbook

Practical requests for messages, files, reactions, channels, events, interactions, modals, and consented direct messages.

Authentication and response envelope

All bot routes use the installation token:

Authorization: Bearer nookbot_…

Bot routes live under /api/v1/app/*. Paths contain no workspace ID. Success and failure use the standard envelope:

{ "success": true, "data": {}, "requestId": "…" }
{
  "success": false,
  "error": { "code": "APP_SCOPE_MISSING", "message": "…" },
  "requestId": "…"
}

Log requestId, status, and safe error code for diagnostics. Do not log the authorization header or private response data.

The shell examples below read that header from a protected runtime file so the token never appears in command arguments or shell history. Have your secret manager write this file with mode 0600; do not commit it or place it below a web root:

# /run/secrets/nook-bot.curl
header = "Authorization: Bearer <installation-token-from-secret-manager>"

Post a channel message

Requires messages:write and an allowed channel:

curl --config /run/secrets/nook-bot.curl \
  --request POST "https://api.your-nook.example/api/v1/app/messages" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: incident-42-summary-v1" \
  --data '{
    "channelId": "<allowed-channel-id>",
    "body": "Incident 42 is resolved."
  }'

Use a unique idempotency key per logical send (≤255 characters):

  • same key + same body after success → HTTP 200 with the original message and replayed: true;
  • same key + different body → 409 APP_IDEMPOTENCY_KEY_INVALID;
  • duplicate while the first request is in flight → 409 CONFLICT, honor Retry-After: 1;
  • a crashed attempt unlocks after approximately 30 seconds.

Rich blocks and components

Blocks require messages:write; actionable components also require interactions:respond:

{
  "channelId": "<channel-id>",
  "body": "Deployment approval required",
  "fallbackText": "Approve or reject deployment 184",
  "blocks": [
    { "type": "section", "text": "Deploy build 184 to production?" },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "actionId": "deploy.approve",
          "label": "Approve",
          "style": "primary",
          "value": "184"
        },
        {
          "type": "button",
          "actionId": "deploy.reject",
          "label": "Reject",
          "style": "danger",
          "value": "184"
        }
      ]
    }
  ]
}

Keep actionId stable and namespaced. Treat every click as untrusted input, verify the callback signature, validate the shared schema, and authorize the business action in your own system.

Read channels, history, and members

GET /api/v1/app/channels
    scope: channels:read

GET /api/v1/app/channels/:channelId/messages?limit=<bounded>
    scope: messages:read

GET /api/v1/app/members
    scope: members:read

Results are filtered by current installation policy. Member results expose display identity and roles needed for the app, never email addresses. Do not cache visibility beyond the time your action needs; policy can change immediately.

Edit or delete your own message

PATCH  /api/v1/app/messages/:messageId
DELETE /api/v1/app/messages/:messageId
scope: messages:write

An app can mutate only its own installation-attributed messages. Store returned message IDs rather than searching channel history to rediscover them.

Reactions

Requires reactions:write:

POST   /api/v1/app/messages/:messageId/reactions
body:  { "emoji": "👍" }

DELETE /api/v1/app/messages/:messageId/reactions/:emoji

The bot adds/removes only its own reaction. An optional scope may be absent; degrade without failing the primary workflow.

Upload and attach a file

Requires files:write. Upload first:

curl --config /run/secrets/nook-bot.curl \
  --request POST "https://api.your-nook.example/api/v1/app/files" \
  --form "file=@./report.pdf;type=application/pdf"

When the instance uses the V2 file pipeline, the returned file is a private installation-scoped draft. Claim it atomically by including up to ten IDs in the message send:

{
  "channelId": "<channel-id>",
  "body": "Here is the report.",
  "fileIds": ["<uploaded-file-id>"]
}

Only the uploading installation may claim the draft. Expired, cancelled, already-claimed, or foreign drafts fail the send. If V2 files are disabled, omit fileIds; the server answers 400 rather than pretending to attach.

Commands

Declare typed options in the manifest. A command callback contains:

  • immutable manifest command name;
  • workspace-local invocation alias for display only;
  • resolved string, number, boolean, user, channel, or other supported option values;
  • current user/channel context;
  • response token and interaction ID.

Answer inside three seconds:

{ "kind": "ephemeral", "text": "Creating the report…" }

Then use the response token for slower work:

const response = await fetch(`${apiBaseUrl}/api/v1/interactions/${interaction.id}/followups`, {
  method: 'POST',
  headers: {
    authorization: `Bearer ${interaction.responseToken}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({ kind: 'message', text: 'The report is ready.' }),
});

if (!response.ok) throw new Error(`Follow-up failed with ${response.status}`);

The response token expires after 15 minutes and allows at most five follow-ups.

Components and modals

Component callbacks include actionId, optional value, actor, and channel or DM context. An immediate ack is useful when processing continues asynchronously:

{ "kind": "ack" }

Open a modal immediately when interactions:respond + modals:open are granted:

{
  "kind": "modal",
  "modal": {
    "title": "Send feedback",
    "inputs": [
      { "type": "text", "id": "subject", "label": "Subject", "required": true },
      { "type": "textarea", "id": "details", "label": "Details" },
      {
        "type": "select",
        "id": "rating",
        "label": "Rating",
        "required": true,
        "options": [
          { "label": "Great", "value": "great" },
          { "label": "Needs work", "value": "needs-work" }
        ]
      }
    ]
  }
}

Delayed modal open is unsupported. Validate submitted values against your own business rules after the shared callback schema.

Events

Declare supported event names and events:receive. Events arrive at the event URL with at-least-once delivery. Common examples include message and mention events; dm.message.created additionally requires dms:write and current DM consent.

{
  "type": "event.app.mentioned",
  "meta": {
    "installationId": "…",
    "idempotencyKey": "stable-logical-event-key"
  },
  "message": {
    "id": "…",
    "channelId": "…",
    "authorUserId": "…",
    "body": "@Acme Helper status?"
  }
}

Dedupe before side effects. Return success only after durable acceptance, or Nook may retry.

Send a consented DM

Requires dms:write and current interaction-derived consent:

curl --config /run/secrets/nook-bot.curl \
  --request POST "https://api.your-nook.example/api/v1/app/dms" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: reminder-<recipient>-<logical-event>" \
  --data '{
    "userId": "<consented-user-id>",
    "text": "Your requested export is ready."
  }'

There is no cold-DM scope or administrator bypass. Treat every APP_DM_NOT_ALLOWED as terminal until the user performs a new permitted interaction. Per-recipient and installation daily limits apply.

Error-handling policy

ClassAction
401 token/signatureStop, reject, and investigate; do not retry with the same unknown authority
403 runtime/scope/DM gateStop the affected action until policy or consent changes
404 channel/resourceTreat as unknown or invisible; do not enumerate
409 idempotency/in-flightCompare code; replay same logical request or honor Retry-After
413 file quotaStop upload and surface an operator-actionable quota error
422 interaction responseFix request shape/kind; do not retry unchanged
429Honor Retry-After with bounded jitter and the original idempotency key
5xx/networkRetry boundedly only for an idempotent/deduplicated operation

Continue with Deploy and operate bots before production use.

Was this guide clear?

Keep commands tied to your Customer Portal values and never paste secrets into support requests.