API v1

REST and JSON. The Onix app itself uses this same API, so everything you see in the app comes through these endpoints.

Base URL: https://onix.sassly.ai/api/v1. All paths on this page are relative to the base URL. Request bodies are JSON (Content-Type: application/json).

The full specification in OpenAPI 3.1 format: /docs/openapi.yaml — import it into Postman, Insomnia, or an SDK generator.

Authentication

There are two ways to authenticate, depending on who's calling:

The Onix app (browser)Integrations (your server)
IdentitySession cookie after signing in with GoogleHeader Authorization: Bearer onx_live_…
Requests that change dataX-CSRF-Token header required; the app sends it automaticallyNo CSRF
AccessFollows the user's role and channel accessThe whole workspace, limited by the key's scope
PlansAll plansPro and Custom

Creating an API key

  1. Open Settings → API (requires Manage API keys access; Pro only).
  2. Click Create key, give it a name (e.g. "CRM integration"), and choose a scope: read (read only) or write (read and change data).
  3. Keys start with onx_live_ and are shown only once. Store yours in a secret manager; never put it in frontend code or a repository.
  4. Up to 10 active keys per workspace. Revoke keys you no longer use; requests with a revoked key get 401.
export ONIX_API_KEY="onx_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

An API key acts on behalf of the workspace and isn't limited by roles, so treat it like a password. Right now every endpoint you can call with an API key only reads data; the write scope is reserved for endpoints that change data (such as sending messages or creating tickets) arriving in upcoming releases. The API doesn't support CORS: call it from your server, not from JavaScript in a browser.

Response format

Successful responses always look like {"data": …, "meta": {…}}; meta is only included when relevant, such as pagination or quota usage. Errors always look like this:

{
  "error": {
    "code": "validation_error",
    "message": "The workspace name must be 2–120 characters.",
    "fields": {"name": "The workspace name must be 2–120 characters."}
  }
}

Rate limits

Error codes

HTTPcodeMeaning
401unauthorizedThe API key is missing, malformed, or revoked; or the app session has expired.
402plan_requiredThe workspace isn't on an active Pro or Custom plan (API keys and Pro-only features).
403forbiddenNo permission for this action, an app-only endpoint was called with an API key, or a read key tried to change data.
404not_foundThe endpoint or data doesn't exist, including data that belongs to another workspace.
405method_not_allowedThis endpoint doesn't support the method; see the Allow header.
419csrf_mismatchAn app request other than GET without a valid X-CSRF-Token. Reload the page and try again.
422validation_errorInvalid input; per-field details are in error.fields.
429rate_limitedMore than 120 requests per minute per key; wait as indicated by Retry-After.
500server_errorA problem on our side. Safe to retry a little later.

Endpoints

The Permission column shows the access an app user needs (granted through their role); "—" means any workspace member can call it. An API key counts as having every workspace permission.

Available with an API key

EndpointPermissionDescription
GET /me—The user, active workspace, role, and permissions. With an API key: the user is the key's creator and via is "key".
GET /workspace—The active workspace: name, plan, Pro end date, time zone, and plan limits.
GET /settingssettings.workspaceThe workspace's General settings (such as the time zone), with the allowed values in meta.choices.
GET /dashboarddashboard.viewThe first steps (Get started with Onix) and plan quota usage.
GET /channels—Channels the user can access, with the channel quota. WhatsApp numbers appear here once the WhatsApp channel is available.
GET /memberssettings.members or settings.rolesAll members including the Owner; meta.users holds user-quota usage and the limit.
GET /invitationssettings.membersInvitations that haven't been accepted yet, including expired ones.
GET /rolessettings.roles or settings.membersAll roles with their permissions and how many people use them.
GET /roles/{id}settings.roles or settings.membersA single role.
GET /permissionssettings.roles or settings.membersThe permission catalog grouped by menu; meta.grantable lists the permissions this user may grant.
GET /audit-logssettings.auditThe audit log, newest first. Filters: category, user (ID), from, to (YYYY-MM-DD), q. Paginated, 50 per page by default.

App only

These endpoints only accept an app session (cookie + X-CSRF-Token). Calling them with an API key returns 403 forbidden.

EndpointPermissionDescription
POST /me/preferences—Save personal preferences, such as tour status and the sidebar.
GET /workspaces—Workspaces where the user is an active member (for the workspace switcher).
PATCH /workspacesettings.workspaceRename the workspace: {"name": "…"}.
PATCH /settingssettings.workspaceUpdate some General settings, e.g. {"timezone": "Asia/Jakarta"}.
GET /notifications—The 20 latest notifications and the unread count.
POST /notifications/read—Mark one ({"id": 12}) or all notifications as read.
POST /realtime/token—A WebSocket token for live updates in the app.
PATCH /members/{id}settings.membersChange a member's role, channel access, or status (active/disabled).
POST /invitationssettings.membersInvite a member: email, role_id, channel_access, channel_ids.
POST /invitations/{id}/resendsettings.membersResend an invitation with a new link.
DELETE /invitations/{id}settings.membersCancel an invitation.
POST /rolessettings.rolesCreate a role: name and permissions.
PATCH /roles/{id}settings.rolesChange a role's name and permissions (except Owner).
DELETE /roles/{id}settings.rolesDelete a role that nobody uses.
POST /roles/{id}/duplicatesettings.rolesCopy a role.
GET /api-keyssettings.apiActive API keys, without their secret values.
POST /api-keyssettings.apiCreate a key: {"name": "…", "scope": "read"}. The full value is returned only once.
DELETE /api-keys/{id}settings.apiRevoke a key.

Examples

Who owns this key

curl -s "https://onix.sassly.ai/api/v1/me" \
  -H "Authorization: Bearer $ONIX_API_KEY"
{
  "data": {
    "user": {"id": 12, "name": "Budi Santoso", "email": "[email protected]", "avatar_url": null, "locale": "en"},
    "workspace": {
      "id": 3,
      "name": "Toko Budi",
      "slug": "toko-budi",
      "plan": "pro",
      "pro_until": "2026-11-08T10:15:00+07:00",
      "timezone": "Asia/Jakarta",
      "limits": {"channels": 4, "users": 10, "tickets_per_month": 200, "replies_per_day": 0, "ai_per_month": 2000, "ai": true, "api": true},
      "created_at": "2026-10-08T09:00:00+07:00"
    },
    "role": null,
    "is_owner": true,
    "is_platform_admin": false,
    "permissions": ["dashboard.view", "settings.members", "settings.roles", "settings.billing", "settings.audit", "settings.workspace", "settings.api"],
    "channel_ids": null,
    "preferences": [],
    "realtime": true,
    "via": "key"
  }
}

In limits, a value of 0 means unlimited. A channel_ids value of null means access to all channels.

List members

curl -s "https://onix.sassly.ai/api/v1/members" \
  -H "Authorization: Bearer $ONIX_API_KEY"
{
  "data": [
    {
      "id": 1,
      "user": {"id": 12, "name": "Budi Santoso", "email": "[email protected]", "avatar_url": null},
      "role": {"id": 1, "name": "Owner"},
      "status": "active",
      "disabled_reason": null,
      "channel_access": "all",
      "channel_ids": [],
      "is_owner": true,
      "is_me": true,
      "editable": false,
      "joined_at": "2026-10-08T09:00:00+07:00"
    },
    {
      "id": 2,
      "user": {"id": 15, "name": "Rina Wulandari", "email": "[email protected]", "avatar_url": null},
      "role": {"id": 3, "name": "Agent"},
      "status": "active",
      "disabled_reason": null,
      "channel_access": "all",
      "channel_ids": [],
      "is_owner": false,
      "is_me": false,
      "editable": true,
      "joined_at": "2026-10-08T10:31:00+07:00"
    }
  ],
  "meta": {"users": {"used": 2, "limit": 10}, "quota_error": null}
}

Member audit log since the start of the month

curl -s "https://onix.sassly.ai/api/v1/audit-logs?category=anggota&from=2026-10-01&per_page=20" \
  -H "Authorization: Bearer $ONIX_API_KEY"
{
  "data": [
    {
      "id": 41,
      "action": "member.invited",
      "label": "Invited a member",
      "category": "anggota",
      "icon": "user-plus",
      "target": "[email protected]",
      "details": "Role: Agent",
      "user": {"id": 12, "name": "Budi Santoso", "avatar_url": null},
      "ip": "203.0.113.10",
      "created_at": "2026-10-08T10:20:00+07:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 20,
    "total": 1,
    "has_more": false,
    "categories": [{"key": "workspace", "label": "Workspace & settings"}, {"key": "anggota", "label": "Members & invitations"}, "…"],
    "users": [{"id": 12, "name": "Budi Santoso"}]
  }
}

Category keys (such as anggota) are fixed identifiers; their display labels follow the language.

A full OpenAPI 3.1 reference for every endpoint, with JavaScript and PHP examples, is coming soon. Endpoints for the WhatsApp inbox, tickets, contacts, and the knowledge base will be added along with those features.