Skip to content
AXOWLDevelopers
Quickstart

Integrations

Integration modules (e.g. SAP/ERP connectors) are enabled per org and gated by scoped permission grants. Base path: /api/org/{slug}/integrations (authenticated org member). Routes marked Owner/Admin refuse other members with 403.

Method · RoutePurpose
GET /enabledList enabled modules: [{ moduleKey, enabledAt, enabledByEmail }]
POST /{moduleKey}/enableEnable a module (Owner/Admin, passkey seal). Body: { assertionResponseJson }. Returns { moduleKey, enabledAt, enabledByEmail }; enabling a module that is already on returns the existing row.
DELETE /{moduleKey}/enableDisconnect a module (Owner/Admin, passkey seal). Body: { assertionResponseJson }. 204 on success, 404 when the module is not enabled.

The seal is a passkey assertion bound to this organization, this module and the direction (enable or disconnect), so a seal made for one cannot be replayed for the other. A missing assertion answers 400; one that fails verification answers 401 with the reason in error.

StatuserrorWhen
402ADDON_REQUIREDThe connector is a paid add-on the organization does not own. addon names the add-on key.
409ROOST_DERIVEDroost turns on by itself once a box is bound to the organization; it cannot be enabled or disconnected here.

Paid connector add-ons are bought through the one add-on door, POST /api/org/{slug}/settings/addons/{addonKey}. There is no separate per-module purchase route.

The same two routes install and remove a marketplace app, with moduleKey = app:{groupKey} — see Selling on the marketplace for the codes specific to apps (APP_NOT_ON_MARKETPLACE, OWN_APP, PAYMENT_REQUIRED, 202 with endsAt on uninstall).

Reading what a connector sends (setup wizard)

Section titled “Reading what a connector sends (setup wizard)”
Method · RoutePurpose
GET /verified-connectors{ verified } — the connectors that have delivered a signed event (the “connected” mark)
GET /{moduleKey}/connection-status{ verified, eventCount, lastEventAt }
GET /{moduleKey}/inbound-preview?orgPath=&userPath=&emailPath=Values found at those paths in the most recent inbound payloads (up to 300 events): { eventCount, org: [{ value, count }], users: [{ id, email, count, match }] }, where match is existing, new or unknown. Needs org.inbound_event_log.read.
GET /{moduleKey}/fieldsThe field paths seen in recent payloads, for mapping without typing
GET /{moduleKey}/hierarchy-preview?levels=Org paths built from the given comma-separated level fields: { eventCount, levels, paths: [{ path, count }] }
GET /{moduleKey}/org-structureThe connector’s org tree: { available, source, discoveredFrom, receivedAt, nodes: [{ id, parentId, name, kind, memberCount }] }
GET /{moduleKey}/org-structure/members?unitId=Members of one unit: [{ id, name, email }]. Needs org.inbound_event_log.read.
POST /{moduleKey}/org-structure/materializeCreate the whole tree as Axowl units in one call (Owner/Admin). Body: { renames? } (unit id → new name). Returns { created, skipped, total, tooLarge }; 400 when the connector has sent no org structure.
GET /{moduleKey}/event-typesEvent types actually received: [{ type, count }]
GET /{moduleKey}/notarize-scopes{ scopes } — the inbound events that are sealed into the audit chain (separate from RBAC). Owner/Admin.
POST /{moduleKey}/notarize-scopesAdd scopes to that list. Body: { scopes: [...] }. Returns { added, reactivated, already, total }. Owner/Admin.

Inbound data itself arrives at POST /api/org/{slug}/inbound/{system}. Org structure is never auto-synced; the tree is materialized only on an explicit call.

Some integrations are opened by Axowl on request. An organization asks; Axowl reviews the request.

Method · RoutePurpose
POST /{moduleKey}/requestAsk for access. Needs org.integration_access_request.create. Returns { id, status }; if a request for this module is already Pending or Approved, that one is returned and no new request is created.
GET /{moduleKey}/requestThe latest request for this module: { status, defaultRoleId, reviewedAt }. status is None, Pending, Approved or Denied.

A grant creates (or reuses) one role per connector group and adds the scopes to it.

Method · RoutePurpose
POST /{moduleKey}/permissions/analyzeCheck a set of scopes for conflicts before granting (Owner/Admin). Body: { groupName, scopes, isAllGroups }. Returns { ok, error, conflicts: [{ severity, title, detail, scopes }] }. 502 when the analysis could not be run.
POST /{moduleKey}/permissions/grantGrant scopes to a group (Owner/Admin, passkey seal bound to this organization, module and group). Body: { groupName, scopes, riskLevel, webhookUrl?, assertionResponseJson }. Returns { ok, error, roleId, addedScopes, failedScopes: [{ scope, error }] } — a scope that fails does not stop the others, and ok is false when any failed. A scope fails when it is malformed, is outside the integration’s namespace, carries uses, ttl or exp, or covers an Owner-only scope the caller does not hold. 400 without scopes or assertion, 401 when the seal fails.
POST /{moduleKey}/ai/translate-scopeTurn a plain-language description into scopes (Owner/Admin, Pro plan or higher). Body: { prompt } (up to 2,000 characters). Returns { ok, error, scopes, notes: [{ scope, reason }], recommendedRiskLevel }. 403 UPGRADE_REQUIRED below Pro, 502 when the translation could not be run.

Granted permissions flow through the same RBAC model — see Permission scopes.