Integrations
PurpletGo connects to identity providers, HRIS platforms, and collaboration tools to automate access revocation during offboarding. This page covers the generic integration record API at /api/integrations - it's session-authenticated (your dashboard login, not an API key) and restricted to members with the integrations_manage permission (Super Admin only by default) for anything that creates, changes, or removes a connection.
Every write on this page - POST /, PATCH /:id, DELETE /:id and each provider connect route - also requires a recent step-up (re-authentication in the dashboard). Without one the request is refused with 428 and { "error": "step_up_required" } before any permission or plan check runs.
Most providers are actually connected through a dedicated OAuth or connect flow rather than this generic endpoint - see Provider-specific connections below. The generic endpoints exist mainly for simple, config-only integrations and for reading back what's connected.
For webhook-based event delivery, see Webhooks.
Supported integration categories
| Category | Providers |
|---|---|
| Identity / SSO | Google Workspace, Okta, Azure AD, JumpCloud, OneLogin, Ping Identity |
| Source control | GitHub |
| HRIS | BambooHR, Workday, Rippling, ADP (via a generic HRIS connector) |
| Collaboration | Slack, Microsoft Teams |
| Calendar / scheduling | Google Calendar, Calendly |
| E-signature | DocuSign, Adobe Sign, Dropbox Sign |
| SIEM | Splunk, Datadog, Elasticsearch, Sumo Logic, New Relic, generic HMAC-signed HTTP (managed through /api/siem-targets, not this API) |
| SMS | Twilio |
| IT ticketing | via a generic ticketing connector |
Integration record
Records are stored per-org, one per service_name. For callers who may manage integrations, the list response masks secret fields in config; everyone else gets config: null (see List integrations):
{
"id": "3f1b2c4a-9e2b-4a3f-8c1d-2b7e9f0a1c3d",
"org_id": "...",
"service_name": "Okta",
"connection_status": true,
"config": { "domain": "acme.okta.com", "apiToken": "••••••••" },
"last_sync": "2025-06-26T06:00:00.000Z",
"created_at": "2025-01-10T09:00:00.000Z"
}There's no status enum (connected/degraded/pending), capabilities list, or health-check object - connection_status is a plain boolean, and whether the integration is actually working is inferred from last_sync and whether recent syncs have errored, not exposed as a structured field.
Endpoints
List integrations
GET /api/integrations
Returns every integration record configured for the org. Available to any authenticated user in the org (the offboarding wizard uses it to offer revocation targets), but what you get back depends on your permission:
- With
integrations_manage(Super Admin by default): the full record, with secretconfigfields masked as"••••••••". - Everyone else:
id,service_name,connection_status,last_sync,created_at, andconfig: null- except for a generic SCIM revocation target, which comes back asconfig: { "scim": true }.
Response 200 OK (as a non-manager)
[
{ "id": "3f1b2c4a-...", "service_name": "Okta", "connection_status": true, "config": null, "last_sync": "2025-06-26T06:00:00.000Z", "created_at": "2025-01-10T09:00:00.000Z" },
{ "id": "9e1c2f3a-...", "service_name": "GitHub", "connection_status": true, "config": null, "last_sync": "2025-06-26T06:00:00.000Z", "created_at": "2025-02-01T09:00:00.000Z" }
]Create or update a generic integration
POST /api/integrations
Requires integrations_manage and a step-up (428 without one). Upserts on (org_id, service_name) - posting the same service_name twice updates the existing record rather than creating a duplicate.
{
"service_name": "Splunk",
"connection_status": true,
"config": { "hecUrl": "https://splunk.acme.com:8088", "hecToken": "..." }
}Response 201 Created - the saved record, with config stripped from the response entirely (not just masked).
Update an integration
PATCH /api/integrations/:id
Requires integrations_manage and a step-up (428 without one). Accepts config and/or connection_status; both are optional and merge-replace rather than partially patch nested config keys.
A few settings have their own audited endpoint and can't be changed here: a config that changes one returns 400 naming that endpoint. Among them are the Microsoft 365 out-of-office reply (auto_reply_enabled, on unless set to false: PATCH /api/integrations/:id/auto-reply-setting) and the Google Workspace Drive transfer to the manager (drive_transfer_enabled, off unless set to true: PATCH /api/integrations/:id/drive-transfer-setting). Both take { "enabled": true } or { "enabled": false }, and a reconnect keeps them.
Disconnect an integration
DELETE /api/integrations/:id
Requires integrations_manage and a step-up (428 without one). Deletes the record. For GitHub and Google Workspace, PurpletGo also makes a best-effort attempt to revoke the stored OAuth token upstream before deleting - if that call fails, the local disconnect still succeeds. Every disconnect is written to the audit log as integration_disconnected.
Response 200 OK - { "success": true }. The response is the same when no record with that id exists in your org: there's no 404, nothing is deleted, and no audit entry is written.
Provider-specific connections
These providers use their own OAuth or connect flow instead of the generic POST /api/integrations above - each writes into the same integration record once connected, but the handshake differs per provider:
| Provider | Flow |
|---|---|
| GitHub | GET /api/integrations/github/oauth/start → OAuth redirect → GET /api/integrations/github/oauth/callback |
| Google Workspace | GET /api/integrations/google-workspace/oauth/start → OAuth redirect → callback |
| Google Calendar | GET /api/integrations/google-calendar/oauth/start → OAuth redirect → callback |
| Okta | POST /api/integrations/okta/connect (identityProviders feature - Starter and up) |
| Azure AD | POST /api/integrations/azure-ad/connect (azureAd), POST /api/integrations/azure-ad/sync |
| JumpCloud / OneLogin / Ping Identity | POST /api/integrations/{provider}/connect (extendedIdentityProviders) |
| Slack | POST /api/integrations/slack/connect (slackNotifications) |
| Microsoft Teams | POST /api/integrations/teams/connect, POST /api/integrations/teams/test (slackNotifications) |
| HRIS (BambooHR/Workday/Rippling/ADP) | POST /api/integrations/hris/connect, POST /api/integrations/hris/sync (hrisIntegration) |
| IT ticketing | POST /api/integrations/it-tickets/connect, inbound POST /api/integrations/it-tickets/webhook (itTickets) |
| DocuSign / Adobe Sign / Dropbox Sign | POST /api/integrations/docusign/connect, POST /api/integrations/adobe-sign/connect, POST /api/integrations/dropbox-sign/connect (esignature) |
| Calendly | POST /api/integrations/calendly/connect (exitInterviewScheduling) |
| SMS (Twilio) | POST /api/integrations/sms/connect (smsNotifications) |
| SCIM | POST /api/integrations/scim/setup (scim), GET /api/integrations/scim/config - see SCIM 2.0 |
| SaaS access-revocation connectors (Jira Cloud, Figma, Asana, Linear, GitLab, Notion, Slack Admin, AWS, Zoom, Salesforce, HubSpot, Dropbox, Zendesk) | POST /api/integrations/saas-revocation/connect (no plan gate) |
| Alumni network | POST /api/integrations/alumni/connect (no plan gate) |
The connect routes require integrations_manage (Super Admin by default) and a step-up (428 without one); most are gated behind the plan feature named in the table, but saas-revocation/connect and alumni/connect have no plan gate. Operating an already-connected integration is less strict: azure-ad/sync and hris/sync are open to Admins as well as Super Admins and need no step-up, and GET scim/config (which returns only configured and lastSync) has no role check at all. Most of these are only reachable from the PurpletGo dashboard's OAuth redirect flow rather than being callable as a standalone API for third-party integrators.
SIEM targets aren't integration records: they're managed under /api/siem-targets (GET and POST /, PATCH and DELETE /:id, POST /:id/test, POST /batch-export), session-authenticated, on the Enterprise plan, for members who can manage organisation settings (Super Admins and Admins by default). See SIEM Integration.
A handful of narrower helper endpoints also exist per-provider (status checks, e.g. GET /api/integrations/github/status; cost-tracking, e.g. PATCH /api/integrations/azure-ad/cost; and sync history, e.g. GET /api/integrations/hris/sync-log) - these support the dashboard UI and aren't part of the stable integration API described above.
The public API (/api/v1) can't revoke access or follow a revocation's progress. Revocation runs when someone clicks Nuke Access in the dashboard (POST /api/revoke-access, session-authenticated, for members with the access revocation permission in the offboarding's department scope; it starts the run in the background and answers at once), or automatically when the person's last day ends, if your organisation has that switched on (Settings → General). Moving an offboarding to a stage doesn't revoke anything by itself. Apps are revoked one at a time, each vendor call limited to 30 seconds, and a run starts no new app after 8 minutes. To know when a run finished and how each app went, subscribe to revocation.completed or revocation.failed via webhooks, or check the audit log.
Related
- Offboarding Initiations - starting offboardings, whose access can then be revoked automatically when the last day ends
- Webhooks - event notifications for offboarding, revocation, asset, checklist and document activity
- Audit Log - integration connect/disconnect and revocation history