Skip to content

Support access API

Most people decide support access in the web app: see Support access. These are the endpoints behind that tab, plus the endpoint that serves the provider’s service notices. They are for signed-in people, not API keys.

On a self-hosted installation there is no service provider: the read endpoint answers "available": false with an empty list, and there are no notices.

  • Sign in as yourself and send your session cookie. See Authentication.
  • A signed-in request that changes something must carry an Origin header with the site’s own address (browsers add it; curl needs -H "Origin: https://app.example.com").
  • Deciding and setting the policy need support_access.manage. Reading needs that, settings.manage or settings.view.
Method and path Does Needs Answers
GET /api/v1/settings/support-access Requests, running access, history and the policy support_access.manage, settings.manage or settings.view 200
POST /api/v1/settings/support-access/{id}/approve Approves a pending request. The access starts now support_access.manage 200 with the request; 404, 409, 422
POST /api/v1/settings/support-access/{id}/deny Denies a pending request support_access.manage 200; 404, 409
POST /api/v1/settings/support-access/{id}/revoke Ends a running access at once: the support session’s next request is refused support_access.manage 200; 404, 409
GET /api/v1/settings/support-access/policy The policy, with defaults for what was never set As for the first row 200
PUT /api/v1/settings/support-access/policy Replaces the policy support_access.manage 200, or 202 when sent for approval; 422
GET /api/v1/notices Service notices in force for this workspace Any signed-in person 200

A support session itself can read the first and fifth rows (they are part of the configuration scope) and can change nothing: every decision answers 403 to it.

Terminal window
curl -s https://app.example.com/api/v1/settings/support-access -b cookies.txt
{
"items": [
{
"id": "0199c1f2-…",
"staff_name": "Jane Doe",
"staff_email": "[email protected]",
"reason": "The nightly file load has failed since Monday; I need to see the data source's mapping.",
"ticket_ref": "SUP-1042",
"scope": "configuration",
"duration_minutes": 60,
"status": "pending",
"requested_at": "2026-10-12T09:14:02Z",
"request_expires_at": "2026-10-15T09:14:02Z"
}
],
"policy": { "allow_requests": true, "max_duration_minutes": 480, "allow_full_read": false },
"can_manage": true,
"available": true
}

items holds the last 200 requests, newest first.

Field Meaning
staff_name, staff_email The member of SpeculaPro’s staff who asks, as they were when asking
reason, ticket_ref Why access is needed (at least 20 characters), and the ticket reference when given
scope, duration_minutes What was asked for: configuration or full_read, and 15 to 480 minutes
status pending, approved (the access runs), denied, cancelled (withdrawn by support), expired, revoked (ended by the workspace) or ended (given back by support). A request or an access past its time reads expired straight away
request_expires_at A request nobody decides lapses then: 72 hours after it was made
decided_by_name, decided_at, decision_note The decision, once there is one
granted_scope, granted_minutes, starts_at, expires_at What was actually given, and until when
ended_at, ended_by How it stopped: a name, or system
can_manage You hold support_access.manage
available false on a self-hosted installation
Terminal window
curl -s -X POST https://app.example.com/api/v1/settings/support-access/0199c1f2-…/approve \
-b cookies.txt -H "Origin: https://app.example.com" -H "Content-Type: application/json" \
-d '{"scope": "configuration", "duration_minutes": 30, "note": "Half an hour is enough for this."}'
Body field Meaning
scope Leave out to give what was asked. Narrower is allowed (configuration when full_read was asked); wider is not
duration_minutes Leave out to give what was asked. Shorter is allowed, down to 5; longer is not
note Optional, up to 2,000 characters. Shown to SpeculaPro support

deny takes {"note": "…"} or an empty body. revoke takes no body.

Answer detail Meaning
403 missing permission support_access.manage You can’t decide support access
404 not found No such request in this workspace
409 the request is no longer pending Already decided, withdrawn or lapsed
409 there is no running grant to revoke The access already ended
422 the scope can be narrowed, not widened: configuration was asked for
422 this workspace's support access policy does not allow full read; approve with the configuration scope, or change the policy first
422 the duration can be shortened, not extended: N minutes were asked for
422 this workspace's support access policy allows at most N minutes
422 the duration must be at least 5 minutes

The rules are held by the database function that records the decision, so they apply whatever client calls the endpoint.

{ "allow_requests": true, "max_duration_minutes": 480, "allow_full_read": false }
Field Meaning Default
allow_requests Whether the provider’s staff can ask at all. Off: their requests are refused before anyone in the workspace is told true
max_duration_minutes The longest one access can run, 15 to 480 480
allow_full_read Whether the full_read scope can be asked for and given false

PUT replaces all three. What happens depends on the direction of the change:

  • Tightening (requests off, full read off, a shorter maximum) applies at once: 200.
  • Loosening (requests on again, full read on, a longer maximum) is sent for approval when the workspace’s approval policy covers Security settings (settings.security). The endpoint then answers as every gated endpoint does: 428 without an X-Change-Reason header, 202 once the request is filed. See Change requests API. If the same PUT also tightens something, that part applies at once and the rest waits.

A policy change never ends an access that is already running: revoke it.

GET /api/v1/notices returns what the service provider wants the workspace’s people to know now: planned maintenance, an incident, a change that needs attention. The web app shows them as a banner at the top of every page.

{
"items": [
{
"id": "0199c2a0-…",
"severity": "warning",
"title_en": "Planned maintenance on Saturday",
"body_en": "The service will be read-only from 22:00 to 23:00 UTC.",
"title_fr": "",
"body_fr": "",
"starts_at": "2026-10-12T08:00:00Z",
"ends_at": "2026-10-18T23:00:00Z"
}
]
}
Field Meaning
severity info, warning or critical. Items come most severe first, then newest first
title_en, body_en The text in English
title_fr, body_fr The text in French when the provider wrote it. Empty: show the English
starts_at, ends_at When the notice is in force. ends_at is absent for a notice with no end

Only notices in force now are returned: addressed to this workspace or to everyone, started, not ended and not withdrawn, 20 at most. A workspace never sees a notice addressed to another workspace.