PurrOSDocs

Integrations

Every connection to another system is an integration: a small program that talks to PurrOS through its API and webhooks.

PurrOS ships no built-in connections to specific products. Instead, every connection to another system (a POS, an online store, an HR platform, a timeclock, payroll, accounting, a supplier or sensors) is an integration: a small program you write or install that talks to PurrOS through its API and webhooks.

How integrations work

  • An integration is a separate program: a container next to PurrOS, a serverless function, or a scheduled script. It can be written in any language.
  • It uses only the public API and webhooks. It never touches the database or PurrOS internals.
  • PurrOS never runs integration code, so a broken integration can't slow down or crash PurrOS. It can only affect data within the scopes it was given.
  • Each integration gets its own API key limited to the scopes it declared, and its own webhook secret. Everything it changes is labelled with its name in the audit log.

The manifest

An integration describes itself in a manifest (purros-integration.json):

{
  "name": "store-101-pos",
  "displayName": "Front counter POS",
  "version": "1.0.0",
  "description": "Sends sales, tenders and clock-ins from the POS to PurrOS.",
  "homepage": "https://git.example.com/ops/pos-bridge",
  "scopes": ["organization:read", "sales:write", "cash:write", "time:write", "inventory:read", "people:read"],
  "webhooks": {
    "url": "https://integrations.example.com/pos-bridge/webhooks",
    "events": ["item.updated", "employee.created", "employee.terminated"]
  },
  "config": [
    { "key": "posBaseUrl", "type": "string", "label": "POS API URL", "required": true },
    { "key": "posApiToken", "type": "secret", "label": "POS API token", "required": true },
    { "key": "locationMap", "type": "json", "label": "POS store ID → PurrOS location", "required": false }
  ]
}
FieldNotes
nameUnique, 2–63 lowercase letters, digits or dashes. It can't change after registration.
displayName, version, description, homepageShown to admins and in the audit log
scopesThe minimum the integration needs. See scopes. people:sensitive needs an Owner's approval.
webhooksOptional. The URL and events to receive. Each event needs a scope of its feature (e.g. employee.* needs a people: scope, location.* needs organization:read). See Webhooks.
configSettings the admin fills in when registering: key, type, label, required, and options for select. Types: string, number, boolean, secret (encrypted, masked when read back by admins), json, location (a location ID) and select. The integration reads the values with GET /integrations/self/config.

Registering an integration

Register with the API (permission integrations.manage) or the CLI on the server. Both validate the manifest against the scope and event catalogs and the features that are switched on.

POST /api/v1/integrations
{
  "manifest": { "name": "store-101-pos", "scopes": ["organization:read", "sales:write"], "config": [ … ] },
  "config": { "posBaseUrl": "https://pos.example.com", "posApiToken": "…" },
  "approveSensitive": false
}
purros integrations register --manifest purros-integration.json --config posBaseUrl=https://pos.example.com --config posApiToken=…

The response contains the API key and, when the manifest declares webhooks, the webhook secret. Both are shown only once: put them in the integration's environment. Values given on the command line are converted to the field's type (number, boolean, json). Only an Owner can approve people:sensitive (approveSensitive, or --approve-sensitive).

(A Settings → Integrations page in the web app is planned; it will use the same API.)

Managing integrations

To…API (integrations.manage)CLI
List them, with health and last heartbeatGET /integrationspurros integrations list
See one: manifest, config (secrets masked), API keys, webhook endpointGET /integrations/{id}
Change config valuesPATCH /integrations/{id}/config with {"values": {…}}; null removes a value
Install a new versionPUT /integrations/{id}/manifestpurros integrations update <name> --manifest …
Pause and resumePOST /integrations/{id}:pause, :resumepurros integrations pause|resume <name>
Rotate its API keyPOST /integrations/{id}:rotate-key with graceMinutespurros integrations rotate-key <name> --grace 1h
See what it loggedGET /integrations/{id}/logs?level=error&from=…
See the batches it sent and how many records were rejectedGET /integrations/{id}/batches?rejectedOnly=true
See its webhook deliveriesGET /webhook-endpoints/{id}/deliveries (webhooks.manage)purros webhooks list
Remove itDELETE /integrations/{id}purros integrations remove <name>
  • Pausing makes its API keys fail with 401 and holds its webhooks. When it's resumed, the held events are delivered in order.
  • Rotating issues a new key. The previous keys keep working for graceMinutes (default 60, at most 7 days; 0 revokes them at once), so you can deploy the new key without downtime.
  • Updating the manifest keeps the name, API keys and config values of fields that still exist. Scopes and events are checked again. If the new manifest drops webhooks, the endpoint and its delivery log are removed; if it adds webhooks, the response includes the new endpoint's secret, once.
  • Removing deletes its API keys, config, logs and webhook endpoint. Records it created stay, and the audit log keeps its name.

Every change is recorded in the audit log.

What an integration can call about itself

EndpointPurpose
GET /api/v1/integrations/selfIts registration, scopes and status
GET /api/v1/integrations/self/configConfig values entered by the admin
POST /api/v1/integrations/self/healthHeartbeat, e.g. { "status": "ok", "message": "Last sync 12:05" }. status is ok, warning or error.
POST /api/v1/integrations/self/logsNotes for the integration's log, e.g. { "level": "info", "message": "Imported 842 transactions" }. Levels: debug, info (default), warning, error. Admins read them at GET /integrations/{id}/logs.

Matching records

Integrations match records using external IDs: store the other system's ID in externalId and use the …/external/{externalId} endpoints to look records up or upsert them. Most integrations don't need their own database at all.

If a record needs more than one external ID, for example a badge number in a timeclock as well as an ID in the HR system, use integrationData, a namespace only your integration can write:

"integrationData": { "store-101-pos": { "posEmployeeId": "00417" } }

Compatibility

Integrations depend only on /api/v1, so an integration written today keeps working across every PurrOS 1.x release. See API versioning.

Sharing integrations

The community keeps a directory of integrations people have built and shared. It's a list of links, not code in the PurrOS repository. Integrations in the directory are maintained by their authors, so review the code and scopes before installing one.

Next

On this page