Axuz Developers

Bots and integrations

How a Node bot (Telegram, Discord, WhatsApp) reads and edits Axuz tasks with an axz_bot_ integration token, including scopes, rotation and error handling.

A bot is a service of yours (a Node process connected to Telegram, Discord, WhatsApp or Slack) that talks to Axuz through the API: it reads and edits the tasks and boards of one team, without using anyone’s password. Its credential is the integration token axz_bot_…, issued by a team owner or admin.

How the pieces fit

CredentialWhat it reaches
Session (cookie or JWT) of a team owner/adminManagement under /teams/{teamId}/integrations/** (create, pause, issue tokens)
axz_bot_… tokenOnly /v1/integration/self* on axuz_api and the task/board routes of the map below, on axuz_workspace_api
HMAC signature (whsec_…)Only the inbound webhook POST /integrations/inbound/{webhookId}

The token never manages integrations (not even its own) and never reaches IAM, billing, admin, /me, /platform, files, wiki, feeds or another team. With an axz_bot_ bearer on any other axuz_api route, the answer is 403 before any handler runs.

Step by step

  1. Authenticate as a team owner or admin and keep the JWT in an environment variable (see Authentication). You also need the teamId.

  2. Create the integration. provider is one of custom_bot, github, slack, discord, telegram, whatsapp, webhook. scopes is required; ask only for what the bot uses. metadata is free-form but non-secret (up to 4 KiB; a key that looks like a secret is rejected).

curl -sS -X POST "https://api.axuz.net/teams/$AXUZ_TEAM_ID/integrations" \
  -H "Authorization: Bearer $AXUZ_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"provider":"telegram","name":"Task bot","scopes":["tasks:read","tasks:write","boards:read","comments:write","integrations:read"]}'
  1. Issue the token. An omitted scopes inherits all of the integration’s scopes; a scope outside it is a 400. expires_in_days (1 to 730) is optional but recommended.
curl -sS -X POST "https://api.axuz.net/teams/$AXUZ_TEAM_ID/integrations/$INTEGRATION_ID/tokens" \
  -H "Authorization: Bearer $AXUZ_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"telegram-prod","expires_in_days":90}'

The 201 response carries the token field (axz_bot_ plus 48 characters). It is shown exactly once: Axuz stores only the SHA-256. Copy it straight into your environment’s secret store. Listings show only prefix (for example axz_bot_AbCd) so you can tell tokens apart.

  1. Test the token with the “who am I” route, which requires the integrations:read scope:
curl -sS "https://api.axuz.net/v1/integration/self" \
  -H "Authorization: Bearer $AXUZ_BOT_TOKEN"

It returns integration, team_id and token.scopes (the effective scopes).

  1. Call the task routes on the workspace host, always with the token’s teamId. See Tasks and boards through the API and the runnable example in examples/bot-node/.

Scopes

These are the scopes in integration_catalog.go. There is no wildcard: matching is exact and anything not listed is not issued.

ScopeAllows
tasks:readList, search and read tasks, subtasks and assignees; list task comments
tasks:writeCreate, update, move (reorder) and assign tasks. Never deletes
boards:readRead boards and columns, including a channel’s board
boards:writeCreate and edit boards and columns. Never deletes
comments:writeComment on tasks (target_type must be task)
integrations:readRead the integration itself, its state, scopes and deliveries

A token’s effective scope is the intersection of the token’s scopes and the integration’s: if an admin narrows scopes on the integration, existing tokens lose that permission immediately.

The allowed-routes table (single source: BotRules) maps every call to a scope, for example GET /teams/{teamId}/boards needs boards:read and PATCH /teams/{teamId}/tasks/{id} needs tasks:write. A method or route outside the table is denied, and there is no DELETE for tasks, boards, columns or comments.

Limits

  • One team per token. The teamId in the path must be the token’s; any other is cross_tenant.
  • Never deletes. Removing things is for a person, in the interface.
  • Comments on tasks only. POST /comments with another target_type is denied.
  • No access to IAM, billing, admin, files, wiki and feeds.
  • Integration metadata and names: up to 4 KiB and 100 characters. Up to 10 webhooks per integration.

Rate limit

In the code we read there is no dedicated limit for integration tokens on the task routes. What exists and applies to a bot:

  • In axuz_workspace_api, mutations (POST, PATCH, PUT, DELETE) under /teams/{teamId} are limited to 60 per minute per key, configurable through MUTATION_RATE_LIMIT_PER_MIN. Reads do not count. Over the limit: 429 with {"error":"rate_limited","retry_after":N} and the Retry-After and X-RateLimit-Limit headers. The key today is user plus team; how a bot principal enters that key is not documented yet.
  • On the inbound webhook: 120 per minute per webhook (429).

Always treat 429 as “retry after Retry-After”, without assuming the exact number.

Rotation and revocation

  • Rotate (POST …/tokens/{tokenId}/rotate): issues a new token with the same scopes and ends the old one. grace_seconds (0 to 86400, default 0) lets the old one stay valid only for that window, so the bot can switch without downtime. An already revoked or expired token returns 409.
  • Revoke (DELETE …/tokens/{tokenId}): idempotent, immediate cut-off, no waiting for the cache.
  • Pausing the integration (PATCH with state: "paused") cuts tokens and deliveries immediately; revoked is terminal (a later PATCH returns 409 integration_revoked).
  • Removing the integration (DELETE) is a soft delete: it revokes the tokens and disables the webhooks.

The resolver caches valid tokens for up to 15 seconds, but revocation and pausing invalidate that cache immediately.

What to do on 401, 403 and 429

StatusMeaningBot action
401Token missing, malformed, revoked, expired, or integration paused/removed. The answer is uniform and does not say whichDo not retry. Alert the owner, stop using the token and swap in a new one
403Insufficient scope, route outside the map, different team, or a route that does not accept integration tokensDo not retry. It is a configuration error: ask for the right scope or fix the route
404The item does not exist in this team (it does not reveal existence in others)Treat as “not found”
429Rate limitWait Retry-After and retry with exponential backoff and jitter
5xxServer failureRetry only GET and PATCH; a POST may have been processed

The error body is always {"error":"<code or message>"}.

Best practices

  • Keep the token in a vault or in the server’s environment variables, never in code, the repository, logs, the URL, or a browser or mobile app. A token in a URL leaks into logs and referrers; the API only accepts the Authorization header.
  • One token per environment and per bot, with expires_in_days and the fewest scopes. Rotate on a schedule and whenever a leak is suspected.
  • Idempotency is yours. POST /tasks and POST /comments do not accept Idempotency-Key today (only the channel invite does). Chat providers redeliver events: store the chat message id and ignore repeats, as the example does.
  • Retry with backoff and jitter, honoring Retry-After; never on 401/403; POST only on 429.
  • Fail closed. If the token disappears from the configuration, the bot must not start pretending to work.
  • Do not log the token. In logs, show at most the prefix.

Next steps

  • Tasks and boards through the API: real routes, fields and errors.
  • A complete example in examples/bot-node/, with an isolated HTTP client, a fictional chat adapter and a test against a mock.

Updated