SupaKnock

Developer guide / API v1

Build support into your app.

Add feedback or chat without replacing your accounts. Start your own server below, then connect your backend, website or iPhone app.

Download OpenAPI

Run SupaKnock with Docker Compose

The image is on Docker Hub: taylorarronj/supaknock. It supports both AMD64 and ARM64, including Apple Silicon.

You need Docker Engine with Compose v2, or Docker Desktop. You do not need to clone the source, build an image or install Python. SQLite is included; no separate database is needed to get started. Already running a server? Skip to your first API request.

1. Create a folder and a Compose file

Check that Docker is running, then create a folder for this installation:

docker compose version
mkdir supaknock
cd supaknock

Save the following as compose.yaml in that folder. This example is for trying SupaKnock on the same computer as your browser.

services:
  supaknock:
    image: taylorarronj/supaknock:latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      PUBLIC_BASE_URL: http://localhost:8000
      COOKIE_SECURE: "false"
      DATABASE_PROVIDER: sqlite
      DATABASE_URL: sqlite:///data/feedback.sqlite
    volumes:
      - feedback_data:/data
    stop_grace_period: 20s
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
volumes:
  feedback_data:

supaknock is the service name used by the commands below. The named feedback_data volume keeps your database, encryption key and setup token when the container is replaced. Keep one SupaKnock instance per installation.

2. Download the image and start the server

docker compose pull supaknock
docker compose up -d supaknock
docker compose ps

The first download includes the optional AI runtime, so it can take a few minutes. Model weights are downloaded only if you enable their installation later. Wait for the container to become healthy, then open http://localhost:8000/setup.

Running this on a remote server? localhost in your browser is your own computer. Follow the domain and HTTPS steps before opening setup.

3. Create your administrator account

Read the generated setup token from your running container:

docker compose exec supaknock cat /data/setup.token

Paste it into the setup page and choose your administrator email and password. Keep the token private. Setup closes permanently after the first administrator is created; normal signup creates participant accounts.

Open /login on your server and sign in. Create a project and a board, then continue with your first feedback request, website chat or the free iPhone app. On a phone, use your server’s HTTPS address, not localhost.

Upgrading an existing installation? Change only its image reference to taylorarronj/supaknock:latest, then follow the backup and update steps. These examples name the service supaknock. On an existing installation, use your current service name in the commands and keep its volume and settings. Stop the old service before renaming it so two containers never share the same database.

latest is the current published image. For a repeatable deployment, choose a release tag from Docker Hub’s tags or pin its digest. Pulling an image does not replace a running container; the up -d step applies it.

Use your own domain

For example, https://support.example.com. Point that hostname’s DNS record at your server, then have your reverse proxy provide HTTPS and forward requests to SupaKnock on port 8000. Caddy, Nginx and Traefik can all do this.

  1. In the Compose service’s environment, change PUBLIC_BASE_URL to your exact HTTPS origin and COOKIE_SECURE to "true". Keep the database and volume settings.
  2. If your proxy runs directly on the same host, keep the localhost port binding and forward to 127.0.0.1:8000. If your proxy is another container, put both services on a shared Docker network and forward to supaknock:8000; remove SupaKnock’s ports block. Inside the proxy container, localhost refers to the proxy itself.
  3. Allow WebSocket upgrades on the same route for live chat. Set TRUSTED_PROXY_IPS to only your actual proxy addresses so connection history and rate limits use the client’s IP. Use a stable proxy address; do not set this to *.
  4. Run docker compose up -d supaknock to apply the settings. Open https://support.example.com/setup for a new installation, or /login for an existing one.
# Replace these two values in the existing environment block:
PUBLIC_BASE_URL: https://support.example.com
COOKIE_SECURE: "true"

Use your own hostname in place of support.example.com. The browser’s origin must match PUBLIC_BASE_URL, including any nonstandard port. A mismatch can cause sign-in or save requests to be rejected. Expose the HTTPS proxy publicly and keep the application port private.

This is your own installation. supaknock.com is the project website, and relay.supaknock.com is only the optional iPhone push relay. Neither is the server URL for your self-hosted data.

Back up and update

Run these commands from your installation folder. Before updating, make a backup with a new directory name each time, then copy the complete bundle out of the container:

docker compose exec supaknock python -m feedback.maintenance backup /data/before-update-001
docker compose cp supaknock:/data/before-update-001 ./before-update-001

Store a private copy away from the server. The bundle includes the database and the encryption key needed to restore it. Copying only the live SQLite file is not a complete backup. Preserve your Compose file and any deployment secrets too.

docker compose pull supaknock
docker compose up -d supaknock
docker compose ps

If you pinned a release tag or digest, update the image reference first. Keep the same Compose project, volume and encryption key. Startup applies database migrations. Do not use docker compose down -v to update: it deletes the persistent volume. Switching to PostgreSQL or Supabase does not move existing data; see database setup.

Server not starting?

Check docker compose ps and docker compose logs --tail=100 supaknock. The image’s health check uses /readyz, which checks database connectivity. A port conflict usually means another service already uses host port 8000. Change the host side of the mapping and the local PUBLIC_BASE_URL together, for example 127.0.0.1:8080:8000 and http://localhost:8080.

If startup reports a database or migration error, preserve the existing volume and inspect the error before restarting. Do not delete the database to make startup succeed. Do not post setup tokens, database URLs or application secrets with diagnostic logs.

Your first feedback request

  1. Create your administrator account using the installation steps above. All API calls below go to that server.
  2. In Overview on your server, create a project for your app. Add a board in its project settings.
  3. Create a project API credential with read and participate. Save the secret on your backend when it is shown.
  4. Find the board's ID, then submit for a user your app has already authenticated.

In the examples, SUPAKNOCK_URL is your server origin and SUPAKNOCK_API_KEY is the backend-only project secret. Replace your-project with the project's identifier.

curl "$SUPAKNOCK_URL/api/v1/projects/your-project/boards" \
  -H "Authorization: Bearer $SUPAKNOCK_API_KEY"

Find the intended board in items and keep its returned id as BOARD_ID. If has_more is true, fetch the next page before deciding a board is missing.

curl -X POST "$SUPAKNOCK_URL/api/v1/boards/$BOARD_ID/requests" \
  -H "Authorization: Bearer $SUPAKNOCK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Offline reading",
    "description": "Open saved articles without a connection.",
    "external_user_id": "your-verified-user-id"
  }'

201 returns the new request and its version. It stays private until accepted. SupaKnock creates the project-scoped participant mapping automatically; no separate user-provisioning call is needed.

Derive the external ID from your authenticated backend session. Do not trust an arbitrary browser-supplied ID. Feedback submission has no idempotency key: after a timeout, check that user's recent submissions before resending.

Projects, boards and user IDs

Share a direct project or board link with your users. They can read published requests, then sign in on your server to submit or vote. Only administrators can browse all projects; regular accounts see their own requests.

A project is one app or environment

It contains boards, project credentials and chat settings. A {project} path accepts its stable identifier, such as glean, or its returned UUID.

Use the board's ID in API paths

A board might have the identifier features, but /boards/{board}/requests requires its returned id UUID. Request paths likewise use the returned request UUID. Names are display labels, not IDs.

Keep your existing user IDs exactly as they are

external_user_id is a string of 1–512 characters. Case, spaces and leading zeros matter. The same string in two projects identifies two separate participants. There is no email matching or automatic account linking.

Choose the right credential

Use one authentication transport per request. A project ID, board ID or external user ID never grants access by itself.

Public reads

Individual active projects, boards and accepted requests can be read without a credential when their URL or ID is known. The installation-wide project directory requires an administrator; project keys list only their own project. Cross-origin browser reads use credentials: 'omit' and no Authorization header. Unpublished and archived records remain concealed.

Your application backend

Send Authorization: Bearer fb_…. A project credential grants only its assigned permissions in one project. Keep it off websites and out of mobile builds. Pass the verified external ID in the query for GET/DELETE, or in the JSON body for participant writes. Use your HTTP client's query encoder.

General API CORS does not allow credentialed calls from another website. Call SupaKnock through your backend; do not forward the browser's Origin header.

Accounts on your installation

POST /api/v1/auth/login takes email and password, sets the feedback_session cookie and returns csrf_token. Authenticated POST, PUT, PATCH and DELETE requests need X-CSRF-Token plus Origin equal to the configured server origin. The token is also in the readable feedback_csrf cookie. GET needs no CSRF token.

Registration creates participants, never administrators. Account-session calls omit external_user_id. Sign out with POST /api/v1/auth/logout; changing the password revokes every session.

Native administrator clients

Sign in through POST /api/v1/auth/native/login with email and password and no existing credential. Send the returned ns_ token as Bearer on later calls. The response also includes expiry and installation ID. These administrator-only sessions need no cookie CSRF. Store the token securely and sign in again after expiry.

This is the iPhone administrator app's transport. Your app's customers use the customer-chat exchange below; they do not need administrator accounts.

Chat customers and widget visitors

Send X-Chat-Token: cc_… for a verified customer or cv_… for the original anonymous conversation. Do not also send cookies or Authorization. Browser tokens require their bound website Origin; native customer tokens require no Origin. Neither token grants administration.

Read, edit and vote

A backend integration usually needs read + participate. Paths below follow /api/v1.

Read the board or a user's own requests

GET /boards/{board}/requests lists accepted requests; use order=popular or newest. GET /me/requests?external_user_id=… includes that user's pending and rejected submissions. An owner can read their private request at GET /requests/{id} with the external ID; unrelated users receive 404.

Edit before acceptance

PATCH /requests/{id} takes title, description, current version and external ID. DELETE /requests/{id} takes version and external ID in its query. First acceptance locks creator edits/deletion permanently. Editing a rejected request returns it to pending while preserving its history.

Set a vote; don't toggle it

Use PUT /requests/{id}/vote with {"present":true,"external_user_id":"…"}, or false to remove it. Repeating the same request is safe. Only accepted requests can receive votes; submission does not add one.

Moderation and progress are separate

A manage key can read /moderation, accept/reject a request, edit its content and update its lifecycle. Every edit or transition needs the current version. Accepted requests can be under consideration, planned, in progress or shipped. There is no unaccept action.

Chat for your signed-in users

Enable chat for the project first. Your backend then exchanges its participate credential and verified user ID for short-lived customer access.

POST /api/v1/chat/projects/your-project/customer-sessions
Authorization: Bearer <project-secret>
Content-Type: application/json

{
  "external_user_id": "your-verified-user-id",
  "client_type": "browser",
  "origin": "https://www.example.com"
}

Make this exchange on your server, without an Origin header. The JSON origin must match an enabled website. For a native customer app, use "client_type":"native" and omit the JSON origin.

  1. Return only the resulting cc_ token and expiry to the client. Subsequent calls use X-Chat-Token; browser fetches use credentials: 'omit'.
  2. GET /api/v1/chat/conversations lists that customer's history. To start one, POST there with client_conversation_id, a UUID retained across retries.
  3. POST to /api/v1/chat/conversations/{id}/messages with a UUID client_message_id and body. Retry an unacknowledged send with the same ID and body; conflicting reuse returns 409.
  4. Read messages with ?after=0&limit=25; pass the returned next_sequence as the next after. POST {"sequence":…} to the conversation's /read path after viewing them.

Tokens last one hour. Renew through your authenticated backend; history and message retry identity survive renewal. DELETE /api/v1/chat/session revokes the current customer token. Your app's logout must explicitly end its chat access.

Live updates

POST the conversation ID to /api/v1/chat/tickets, then connect to /api/v1/chat/ws. Send the one-use ticket in the first frame, never in the URL. Tickets last 60 seconds. The OpenAPI document's x-websockets section describes frames and reconnect cursors; HTTP history can also catch up after disconnection.

The supplied widget below uses anonymous visitor sessions. It does not automatically use your app's login or merge an anonymous chat into an account. Build your own customer UI for authenticated history; the repository's docs/chat.md includes the complete flow.

Add the website widget

Set shared business hours in Account settings, then inherit, override or turn them off for each project under Website chat. Outside these hours, visitors can leave a message and, if enabled, an optional email for private manual follow-up. This does not send email or guarantee a live response. Availability is returned in the chat configuration as business_hours.

Choose the layout, scroll reveal, button text and light/dark colors in your website's embed code. Appearance stays on your website. Enable chat and allow website origins in Project settings → Website chat; messages are still saved to your installation.

<div id="suggestion-box-chat"></div>
<script type="application/json" id="suggestion-box-appearance">
{
  "launcherText": "Talk to the Glean team",
  "title": "Glean support",
  "theme": {
    "mode": "auto",
    "light": { "accent": "#37306B", "onAccent": "#FFFFFF" },
    "dark": { "accent": "#E7E1FF", "onAccent": "#221C43" }
  }
}
</script>
<script defer src="https://box.example.com/widget.js"
  data-server="https://box.example.com" data-project="your-project"
  data-target="#suggestion-box-chat"
  data-config="suggestion-box-appearance"></script>

Replace the server and project with those from your project's installation snippet. data-config names the JSON element's ID, without #. The JSON controls appearance; the script's data-server, data-project, and data-target still select where chat connects and mounts.

Bubble, bottom bar, or inline

Add presentation to the appearance JSON or manual mount options: "bubble" keeps the bottom-right launcher (the default), "bar" uses a centered floating bottom bar, and "inline" puts a bar exactly where your target div lives. Only the inline launcher follows the div’s width. The conversation opens above the page, bottom-right on desktop and almost full-screen on mobile. Opening and closing do not move surrounding content.

{
  "presentation": "bar",
  "revealAfterScroll": 600,
  "launcherText": "Ask us anything…"
}

For floating layouts, revealAfterScroll is a positive number of pixels scrolled down the main document. Once revealed, chat stays available when scrolling back up. Omit it or use 0 to show immediately. Inline ignores this setting. Invalid values use immediate display; unknown presentations use the bubble. Choose a reachable threshold for short pages. Reveal does not steal focus and respects reduced motion.

Bars are buttons that open the same conversation and focus its message field. They default to Ask a question…. Your own support button can call SuggestionBoxChat.get('#suggestion-box-chat').open() to bypass the scroll threshold; .close() closes without clearing the draft. Mount one widget per page. All layouts share the same chat session; appearance options are read at mount time.

Light, dark, or automatic

theme.mode accepts auto, light, or dark. Automatic mode follows the operating system. Each palette supports accent, onAccent, background, surface, text, muted, and border. Use #RGB or #RRGGBB colors; omitted or invalid values keep the built-in defaults. Choose readable color pairs.

For your own website theme toggle, call this after the widget loads. It preserves the conversation and draft:

SuggestionBoxChat.get('#suggestion-box-chat').setTheme('dark');
// Use 'light' for light mode, or 'auto' to follow the operating system.

Opening and closing animate gently. Reduced-motion preferences are respected automatically.

Mount from your JavaScript

Alternatively, load widget.js without data-* attributes, then mount it after the script and target element are ready:

const chat = SuggestionBoxChat.mount('#suggestion-box-chat', {
  server: 'https://box.example.com',
  project: 'your-project',
  launcherText: 'Talk to the Glean team',
  theme: {
    mode: 'auto',
    light: { accent: '#37306B', onAccent: '#FFFFFF' },
    dark: { accent: '#E7E1FF', onAccent: '#221C43' },
  },
});
chat.setTheme('light');

Choose automatic embed or manual mount so the target is mounted once. Labels render as plain text, up to 80 characters for launcherText and 120 for title. Omitting them keeps Chat with us for the bubble, Ask a question… for bars, and the project's name as the panel title. Never put a project API credential in your website code.

Responses, retries and limits

Pagination

Most lists accept limit (default 25, max 100) and offset (default 0, max 1,000,000). Responses contain items, has_more, limit and offset. Increase offset by the returned limit until no more remain. Popularity and active-chat ordering can move while you page.

Message history uses sequence cursors. Connected-app users/activity use an opaque continuation. Some small administrator collections return only items. Follow each operation's schema.

Useful errors

{
  "error": {
    "code": "invalid_input",
    "message": "Check the submitted fields",
    "fields": [{"field":"body.title","message":"Field required"}]
  }
}

fields appears on validation errors. Use the stable code for client behavior. 400 means correct input; 401 means authenticate again; 403 means check permissions, origin or CSRF; 404 can mean a concealed record. After 409, reload and review the current state before retrying. A newer version cannot bypass a locked or invalid action.

413 means the body is too large. On 429, wait and honor Retry-After when supplied. On a timeout or 5xx, retry only if that operation is safe to repeat. Feedback submission is not idempotent; vote writes and chat sends with retained client IDs are.

Text and body sizes

Feedback titles allow 200 characters, descriptions 20,000 and chat messages 4,000. Default bodies are 96 KiB; ordinary chat bodies are 32 KiB. Defined Markdown, personality and report-edit routes allow 400 KiB of JSON while retaining their decoded-text limits. Unknown input fields are rejected. Check the operation before building an upload form.

Empty responses and timestamps

A successful delete or logout may return 204 with no body; don't parse it as JSON. Resource timestamps are UTC ISO-8601 strings. Webhook signature timestamps use Unix seconds.

Receive events on your backend

Configure a project webhook in its settings or at /api/v1/projects/{project}/webhook. Save the signing secret when created or rotated. Request events cover creation, edits, acceptance, rejection, status changes and deletion. chat.message_created covers customer, administrator and optional AI messages without including message text.

  1. Verify X-Feedback-Signature using HMAC-SHA256 over the timestamp, a dot and the raw request body. Verify before parsing or reserializing JSON.
  2. Use a constant-time comparison and a five-minute timestamp tolerance. Keep clocks synchronized.
  3. Persist and deduplicate X-Feedback-Event-ID. Return 2xx after safely recording or processing the event. Delivery can repeat or arrive out of order.

Use Send test webhook, then inspect delivery history. Failed jobs can be retried at /api/v1/projects/{project}/deliveries/{id}/retry. Existing jobs keep the original destination and signing secret after settings change; allow old secrets until those jobs finish.

Notify the right people

When a request ships, its event is request.status_changed with status: "shipped". Fetch /api/v1/requests/{id}/audience with audience:read to obtain the current creator/voter identities. Paginate, map external IDs to your accounts and send notifications from your backend. SupaKnock does not send these emails for you.

The full raw-body verification example, event payloads and retry schedule are in the repository's docs/webhooks.md and the OpenAPI specification.

Bring account context into support

Open your app's summaries, users, profiles and activity while answering a customer. SupaKnock reads them when an administrator opens the view. Your app keeps its accounts and events.

  1. Have your app implement the small read-only protocol in docs/protocol/v1/. Give it a permanent app-instance ID and a dedicated read-only token.
  2. Open Project settings → App connection. Save the public HTTPS endpoint and its token, then choose Test saved connection.
  3. Review the app identity and approved link origins. Confirm the support identity namespace only if its user IDs exactly match this project's feedback/chat external IDs.
  4. Enable the connection and open its app data. Recent signups, filters and activity depend on the capabilities and history your app supplies.

These views are administrator-only and read-only. They do not sync accounts, ingest events, send signup alerts or perform marketing actions. Remote responses are not persistently cached or added to AI knowledge. The app-issued outbound token is separate from your SupaKnock project key.

For a custom administrator client

Connection setup uses /api/v1/projects/{project}/app-data/connection and its test/enable/disable/remove actions. Summary, users, profile and activity reads use POST at /app-data/{view}/query with the saved connection version and typed query. Users/activity pages return an opaque continuation; related local support records use offset pages.

Cookie clients need CSRF even for these read-only POSTs. Project and chat credentials cannot access them. Refresh after the connection changes or a continuation expires. The installation library/timezone preferences live under /api/v1/admin/connected-apps.

Which endpoints does my app implement?

Every route authenticates Authorization: Bearer <app-read-token>. Start with discovery and a user directory; add only capabilities your app supports.

  • GET /v1/manifest — app instance, protocol version, capabilities and supported filters.
  • POST /v1/users/query — current accounts ordered by signup time, newest first.
  • POST /v1/users/lookup — an exact user profile when profiles are supported.
  • POST /v1/summary/query — account totals/counting basis for a requested period.
  • POST /v1/activity/query — retained events when activity is supported.

Configure the base address, such as https://app.example.com/support; SupaKnock appends those paths. Each successful JSON response includes protocol, app_instance_id and observed_at. Discovery can advertise a directory-only integration:

{
  "protocol": {"major": 1, "minor": 0},
  "app_instance_id": "your-app-production",
  "observed_at": "2026-10-12T12:00:00Z",
  "capabilities": {"users": true},
  "user_filters": [],
  "link_origins": []
}

An initial users query can be {"limit":50}. User pages use exact IDs, UTC signup times and opaque cursors; summaries distinguish current accounts from retained signup events. Responses use Cache-Control: no-store, are bounded to 1 MiB and must not redirect. The repository's protocol schemas define all required page fields, ordering and cursor rules; the manifest alone is not a complete adapter.

Give your app's developer docs/connected-apps-agent-brief.md to implement the remote side. The installation specification describes the administrator side; these are two separate APIs.

iPhone notifications and the push relay

SupaKnock’s push relay is at https://relay.supaknock.com. It sends optional notifications to the iPhone admin app. Feedback, chat and your app’s API requests still go to your own SupaKnock server.

  1. On your server, open Admin → Push notifications.
  2. Use https://relay.supaknock.com as the relay URL and enable push. Enter just this origin, without /api/v1.
  3. Sign in to your server in the iPhone app, allow notifications and save your device preferences.

To enable push through the API, send this request to your installation. SUPAKNOCK_ADMIN_TOKEN is the ns_ session returned by native administrator sign-in. Project API keys cannot change push settings.

curl -X PUT "$SUPAKNOCK_URL/api/v1/admin/push" \
  -H "Authorization: Bearer $SUPAKNOCK_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true,"relay_url":"https://relay.supaknock.com"}'

Your server enrolls with the relay and stores its credential encrypted. The iPhone app connects to your server; it never receives the relay credential. Notifications use generic text without feedback titles or chat messages. Push is off by default, and feedback and chat work without it.

You can also run your own compatible relay. Choose its HTTPS origin before enabling push: an enrolled installation cannot switch to a different relay through this setting. Existing connections to suggestion-box-relay.doomo.dev remain supported.

Read settings with GET /api/v1/admin/push and inspect attempts with GET /api/v1/admin/push/deliveries, both on your installation. Delivery history reports whether Apple accepted a notification, not whether it appeared on the phone.

Choose where your data lives

SQLite works out of the box. You can also use PostgreSQL or Supabase. Your API clients and project keys work the same way with all three.

DATABASE_PROVIDER=sqlite
DATABASE_URL=sqlite:///data/feedback.sqlite

For PostgreSQL, create a dedicated database and configure a private connection URL. Supply one stable application encryption key before first startup:

DATABASE_PROVIDER=postgres
DATABASE_URL=postgresql://USER:ENCODED_PASSWORD@HOST:5432/DATABASE?sslmode=require
APPLICATION_SECRET=YOUR_FERNET_KEY
DATA_DIR=/data

Using Docker Compose? Generate the key once, before your first remote-provider startup. These commands use Python inside the image, so nothing extra is needed on your computer:

docker compose pull supaknock
docker compose run --rm --no-deps --entrypoint python supaknock -c \
  'from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())'

Save the output as APPLICATION_SECRET in your private .env. If you installed SupaKnock directly with Python, run the same python -c command in that environment. Preserve the exact key across restarts, backups and restores. Never put database credentials in a client.

For Compose, save the values above in a .env file next to compose.yaml. After generating the key, replace the SQLite database entries under your service’s environment with these entries. Compose only passes .env values into a container when the file references them:

DATABASE_PROVIDER: ${DATABASE_PROVIDER}
DATABASE_URL: ${DATABASE_URL}
APPLICATION_SECRET: ${APPLICATION_SECRET}
DATA_DIR: /data

Keep the existing public URL, cookie settings and /data volume. Start the installation with docker compose up -d supaknock after configuring the new database. Do this before creating data you want to keep; provider selection does not transfer it.

Using Supabase

Set DATABASE_PROVIDER=supabase and copy the project's direct or session-pooler PostgreSQL URI on port 5432. Use its database password, not a public or service-role API key. The transaction pooler on 6543 is unsupported. Keep the private supaknock schema out of Supabase's exposed API schemas.

Remote connections require TLS. Keep one app instance and its local volume for setup state and optional model weights. Changing a provider or URL does not migrate existing data. The repository's docs/database-providers.md and docs/operations.md cover connection permissions and backup/restore with matching PostgreSQL client tools.

Every endpoint, field and response

Browse every HTTP operation and schema below, or download the same reference for your API client. This snapshot describes the current release. Your installation’s /api/v1/openapi.json describes the version you actually run.

Open the specification

More administrator features

The specification also covers shared notifications, account activity, project/board archiving, push devices, business hours, AI providers, knowledge and authoring. Those features use administrator accounts; project keys do not grant access. AI, its tools and guest reporting are independently opt-in.

Private connection IPs appear only in administrator views. External IDs, voter identities, private history and credentials are not public. Archived resources may return 404 to other callers. See docs/README.md in the repository for the feature guides.

Browse the complete API

Choose a feature or search a path. Open an operation for its authentication, inputs, response codes and schemas. This is a read-only reference; it does not send API requests on your behalf.

Loading the release API reference…