Skip to content
Apolaris AI Docs

REST API overview

developer

A token-authenticated JSON API over the core resources, scoped to the token owner's domain, sites, and privileges.

The Apolaris AI REST API is a versioned, token-authenticated JSON interface to the same data you see in the app — customers, bookings, sales, services, employees, sites, inventory, and expenses. It lets you build dashboards, sync tools, and automations against live data without driving a browser session or scraping screens, and because it reuses the platform's own tenancy and permission model, your integrations stay safe by construction.

  • Base URL: /api/v1/ (for example https://your-domain/api/v1/)
  • Interactive docs: /api/docs/ (Swagger UI) — click Authorize, paste your token, and try endpoints live. The raw OpenAPI spec is at /api/schema/. The docs require a logged-in account whose organization has the API module enabled, and show only the operations your account can authorize.

The API is a separately-enabled module, off by default. Until an administrator enables it for your organization, every /api/v1/ request returns 403 api_disabled — even with a valid token — and the developer docs stay hidden. Ask your administrator to turn on Developer REST API in Module settings.

Authenticate

The API uses a personal API token, not the session cookie.

  1. In the app, open Account → API tokens (/customers/account/api-tokens/) and click Generate token. The raw token is shown once and looks like ztk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx — copy it immediately.
  2. Send it as a bearer token on every request:
curl -H "Authorization: Bearer ztk_your_token_here" \
     https://your-domain/api/v1/me/

X-API-Key: ztk_… is also accepted as a fallback.

Scopes

When you generate a token you choose its scopes — an allow-list of resource:action permissions such as customers:read, bookings:write, or sales:read. Grant only what the integration needs. Leaving every scope unchecked with Full access on issues an unrestricted token.

A scope can only ever narrow a token: the effective permission of a request is the intersection of the token's scopes and the owner's can_* privileges, so a token can never do more than its user — and often does less. A call outside the token's scopes returns 403 insufficient_scope. GET /api/v1/me/ echoes the token's effective scopes; GET /api/v1/meta/ lists every available scope with a description.

Treat tokens like passwords. Store them in a secret manager — never in client-side code or version control — and revoke unused ones. The raw value is never stored and cannot be re-displayed; if you lose it, generate a new one.

Every authenticated call stamps the token's last used time, so the API tokens page shows you which tokens are still active. Revoking a token is a soft delete — it stops authenticating immediately but stays in the list for the audit trail.

Tenancy & privilege scoping

A token acts as its owner and inherits exactly what that user can do in the UI:

  • Organization (tenant): every response is scoped to the user's organization. You can never see another organization's data.
  • Visible sites: non-managers see only their assigned or granted sites. For resources that carry a site (bookings, expenses) this filters directly on the record's site. Sales have no site field, so a non-manager's sales are scoped through the attending employee's sites — you see a sale only if the employee who handled it works at a site you can see. The same rule gates writing a sale: you cannot record one for an employee at a site you cannot access.
  • can_* privileges: reads require the matching can_view_*, writes require the matching add/edit privilege. Managers (is_manager) bypass every read/write gate within their organization.

A token can therefore never do more through the API than its user can in the app, and revoking the token (or disabling the user) stops access immediately. See Users & privileges and the Privileges reference.

Identity & index

GET /api/v1/me/ returns your identity (user, organization), your is_manager flag, the full privileges map, and your visible_site_ids. Start here to confirm a token works.

GET /api/v1/ is a lightweight index you can hit to discover the service and its resource URLs:

{
  "service": "Apolaris AI REST API",
  "version": "v1",
  "authenticated_as": "Jane Doe",
  "domain": "Your Organization",
  "resources": {
    "customers": "/api/v1/customers/",
    "bookings": "/api/v1/bookings/"
    /* services, sales, employees, sites, inventory, expenses */
  },
  "me": "/api/v1/me/",
  "status": "/api/v1/status/",
  "meta": "/api/v1/meta/",
  "activity": "/api/v1/activity/",
  "docs": "/api/docs/",
  "schema": "/api/schema/"
}

Resources

Resource List / Create Detail / Update Read gate Write gate
Customers GET, POST /customers/ GET, PATCH /customers/{id}/ can_view_customer can_add_customer / can_edit_customer
Bookings GET, POST /bookings/ GET, PATCH /bookings/{id}/ can_view_booking can_add_booking / can_edit_booking
Sales GET, POST /sales/ GET /sales/{id}/ can_view_sale can_manage_sales
Services GET /services/ GET /services/{id}/ can_view_service —
Employees GET /employees/ GET /employees/{id}/ can_view_employee —
Sites GET /sites/ GET /sites/{id}/ can_view_site —
Inventory GET /inventory/ GET /inventory/{id}/ can_view_reports —
Expenses GET /expenses/ GET /expenses/{id}/ can_view_reports —

Customers, bookings, and sales support privilege-gated writes; the rest are read-only.

Filters are per-resource, not a shared set — only pass a filter the endpoint actually supports (unknown query parameters are ignored). The interactive /api/docs/ reference lists the exact parameters for each operation:

Resource Filters on GET (list)
Customers ?q= (name / email / phone)
Bookings ?site=, ?employee=, ?customer=, ?status=, ?date_from=, ?date_to=
Sales ?employee=, ?customer=, ?payment_status=, ?date_from=, ?date_to=
Services ?q=, ?category=, ?is_active= (0/1)
Employees ?q=, ?site=, ?is_active= (0/1)
Sites ?q= (name / address)
Inventory ?q= (name / SKU), ?category=, ?low_stock=1
Expenses ?site=, ?category=, ?status=, ?date_from=, ?date_to=

?status= filters bookings and expenses; it does not apply to sales. Sales use ?payment_status= instead. Passing the wrong one simply has no effect — it does not raise an error.

Creating a booking with customer details (instead of an existing customer_id) and no match found will mint a walk-in customer, exactly as the calendar UI does. This rides on can_add_booking. Pass a customer_id to avoid creating new records.

Module endpoints (Cameras, Contact Center, AI Copilot)

These extend the API to the optional product modules so you can build integrations. Each one also requires its product module to be enabled for your organization (on top of the API module); when the module is off the endpoint returns 403 module_disabled. They carry their own scopes.

Resource Endpoints Scope Access Module
Cameras GET /cameras/, GET /cameras/{id}/ cameras:read manager Cameras
Camera events GET /camera-events/, GET /camera-events/{id}/ cameras:read manager Cameras
Conversations GET /conversations/, GET /conversations/{id}/ ccaas:read can_use_ccaas / manager Contact Center
Messages GET, POST /conversations/{id}/messages/ ccaas:read / ccaas:write can_use_ccaas / manager Contact Center
AI chat POST /assistant/chat/ assistant:chat manager AI Copilot
AI insights GET /assistant/insights/ assistant:read manager AI Copilot
  • Cameras are read-only. The detection-event feed (/camera-events/) filters on ?type=, ?camera=, ?site=, ?date_from=, ?date_to= and the ?after= id cursor for incremental polling. RTSP URLs/credentials are never returned.
  • Contact Center: list/read conversations and their messages, and POST /conversations/{id}/messages/ to send a reply (this switches the conversation to human mode so the AI agent stops auto-replying). Provider session ids and secrets are never returned.
  • AI Copilot: POST /assistant/chat/ with {message, thread_id?} returns {thread_id, reply, ui_actions}. It is read-only over the API — the copilot can answer and analyse but cannot create or modify records (do writes through the explicit endpoints above). It is manager-only because the copilot reads domain-wide data.

  • Analytics (analytics:read, manager, module Analytics): GET /analytics/summary/ returns aggregate KPIs (revenue, bookings, expenses, net profit, inventory value, top services) for a period (?date_from=, ?date_to=, ?days=, ?site=). Safe aggregates only — no PII.

  • Loyalty (loyalty:read / loyalty:write, can_use_loyalty/manager, module Loyalty): GET /loyalty/accounts/ (+{id}/ with tier + ledger), GET+POST /loyalty/accounts/{id}/transactions/. The POST awards/redeems points ({txn_type: earn_manual|adjust|redeem, points, reason}) through the canonical funnel — redeem/adjust can never drive the balance negative.
  • Discounts (discounts:read, can_manage_discounts/manager, module Discounts): GET /discount-codes/ (+{id}/), and POST /discount-codes/validate/ ({code, subtotal, customer_id?}) to check a code without redeeming it — ideal for an external checkout.
  • HR (read-only, hr:read): GET /departments/, GET /leave-types/, GET /leave/. Leave is scoped: approvers/managers see the whole organization, everyone else sees only their own.
Resource Endpoints Scope Access Module
Analytics GET /analytics/summary/ analytics:read manager Analytics
Loyalty GET /loyalty/accounts/ (+{id}/, +{id}/transactions/), POST .../transactions/ loyalty:read / loyalty:write can_use_loyalty/manager Loyalty
Discounts GET /discount-codes/ (+{id}/), POST /discount-codes/validate/ discounts:read can_manage_discounts/manager Discounts
HR GET /departments/, /leave-types/, /leave/ hr:read manager / can_view_employee / vacation perms —

The enum vocabularies for these (camera_event_type, conversation_status, conversation_mode, message_direction, message_status, loyalty_txn_type, discount_type, leave_status) are listed by GET /api/v1/meta/.

Responses, pagination & errors

List endpoints are paginated. Control them with ?page=N and ?page_size=N (default 50, max 200):

{
  "count": 128,
  "page": 1,
  "num_pages": 3,
  "page_size": 50,
  "next": 2,
  "previous": null,
  "results": [ /* objects */ ]
}

List endpoints also accept ordering (comma-separated whitelisted keys; prefix - for descending), fields / exclude (sparse fieldsets — id is always kept), and the time windows created_since / created_until / updated_since (ISO date or datetime). See each endpoint's parameters in /api/docs/.

Detail, create, and update return the single object as JSON; creates return HTTP 201. Errors use a consistent envelope (documentation_url points back to these docs):

{ "error": "insufficient_scope", "detail": "This token is not authorized for the \"customers:write\" scope.", "documentation_url": "/api/docs/" }
Status error Meaning
400 validation Bad or missing fields, or unparseable JSON body.
401 unauthorized Missing or invalid/revoked token.
403 forbidden Authenticated, but the owner lacks the required privilege.
403 insufficient_scope The token is not authorized for the operation's scope.
403 api_disabled The API module is not enabled for your organization.
403 domain_suspended The organization's account is suspended — every API call returns {"error": "domain_suspended"} until it is reinstated. Have your integration back off and contact support; retrying will not help.
404 not_found No such object in your organization.
405 method_not_allowed HTTP verb not supported on this endpoint.
429 rate_limited Too many requests — see Retry-After and the X-RateLimit-* headers.
500 server_error Unexpected error.

Validation detail strings

The detail text on a 400 validation response is stable, so you can branch on it. The ones you are most likely to hit:

detail When
first_name is required. Creating a customer without a first_name.
A customer with this email already exists. The email collides with an existing customer — email is unique per organization (returned on both create and update).
amount must be a number. Creating a sale with a non-numeric amount (and no line items).
Request body must be valid JSON. The request body is not parseable JSON.

Booking create/update returns validation with the message from the calendar's own assignment, conflict, and on-leave guards (the API runs the same checks as the UI).

Notes & limits

  • Read-replica safe. GET endpoints never write, so they work against a read-only failover node.
  • OPTIONS preflight (CORS). An OPTIONS request to any endpoint is answered without a token and returns an Allow header listing the supported verbs (always including OPTIONS). This is what browsers send before a cross-origin call — it never exposes data, so it does not require authentication.
  • Rate limiting is enforced per token. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix epoch); exceeding the limit returns 429 rate_limited with a Retry-After header. Back off when Remaining reaches 0.
  • Caching. Read responses send Cache-Control: private (they embed your identity — never cache them in a shared proxy); identity and write responses send no-store. GET /api/v1/meta/ is cacheable for a few minutes.
  • Idempotent creates. POST creates accept an optional Idempotency-Key header. A retry with the same key replays the original response instead of creating a duplicate — use a fresh UUID per logical create.
  • Versioning. This is v1. Breaking changes will ship under a new version prefix; additive fields may appear within v1.

Developer utility endpoints

Endpoint Purpose
GET /api/v1/status/ Health/smoke test — confirms the token authenticates and the module is on; reports the rate-limit policy and server time.
GET /api/v1/meta/ Reference vocabularies for building integrations: enum values, currencies, languages, available scopes, your organization settings and accessible sites.
GET /api/v1/activity/ Recent activity feed — the most recently created/updated bookings and sales you can see (limit, since params). Respects your scopes.

Tips

Start with GET /api/v1/me/ to confirm your token, see your is_manager flag, and read your visible site ids before building queries.

  • Use /api/docs/ (Swagger UI) to explore and test endpoints interactively before writing code.
  • Page through large lists with page_size=200 and follow the next field until it is null.
  • Ready to build? See API examples for copy-paste curl and Python, and Integrations & webhooks for connecting external systems.