Skip to content

Support access ​

Generated from DialerDigital/core commit 0fc18008b3c8 — spec openapi/dialer-v1.yaml v0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.

Español

Platform-support access (ADR 225): the customer's consent (a grant with a reason and a fixed expiry, one live per tenant, revoked at will) and the append-only trail of it. account_admin only, except the banner.

Endpoints ​

MethodPathSummary
POST/v1/support-access/grantsLet platform support in, with a reason and a fixed expiry
DELETE/v1/support-access/grants/{id}End the consent at once
GET/v1/support-access/eventsThe trail of platform-support access, newest first
GET/v1/support-access/activeWhether a support session is open right now (the banner)

Let platform support in, with a reason and a fixed expiry ​

http
POST /v1/support-access/grants

The customer's consent to platform-support access (ADR 225). An account_admin states a reason (a ticket or a motive, 1 to 500 characters, no control characters) and, optionally, a duration_minutes (15 to 480, default 60). The grant expires at created_at + duration_minutes and that expiry NEVER moves: a longer access is a new grant.

One grant is live per tenant at a time: while one is neither revoked nor expired the answer is 409 grant_already_live, naming it. A grant_created event is written in the same transaction.

This route does not open anything by itself: a grant is the consent a support session needs, and the session lane is gated by the support_access_enabled flag, which is OFF in every cell. The route answers whatever the flag says, so a customer can always grant, revoke and read the trail.

account_admin dashboard login only (see 403). Platform staff have no route to write a grant.

Request body (JSON, required)

FieldTypeRequiredDescription
reasonstringyes
duration_minutesintegerno

Responses

201 ​

The grant is live.

FieldTypeRequiredDescription
grantobject (SupportAccessGrant)yes
grant.idstring (uuid)yes
grant.granted_bystring (uuid)yesThe account_admin who granted it.
grant.reasonstringyes
grant.duration_minutesintegeryes
grant.created_atstring (date-time)yes
grant.expires_atstring (date-time)yesAlways created_at + duration_minutes; it never moves.
grant.revoked_atstring (date-time) | nullyes
grant.revoked_bystring (uuid) | nullyes

400 — BadRequest ​

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 ​

role_forbidden: anything but a dashboard login (ddu_) whose person has the account_admin role — a dd_ API key and a member login included; forbidden: a widget token (ddw_) or a suspended tenant.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "granting platform-support access takes a dashboard login with the account_admin role"
  }
}

409 ​

grant_already_live: the tenant already has a grant that is neither revoked nor expired; revoke it first.

json
{
  "error": {
    "code": "grant_already_live",
    "message": "a support-access grant is already live for this tenant; revoke it before granting again"
  }
}

422 ​

unprocessable: the reason is missing, blank, over 500 characters or has control characters, or duration_minutes is not an integer from 15 to 480. Nothing is written.

json
{
  "error": {
    "code": "unprocessable",
    "message": "duration_minutes must be an integer from 15 to 480"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

End the consent at once ​

http
DELETE /v1/support-access/grants/{id}

Revokes the grant id. The row is kept as evidence: it gains revoked_at and revoked_by, once, and a grant_revoked event is written in the same transaction. A grant that expired by itself can still be revoked. A revoked grant is 409 grant_already_revoked.

An unknown id, a malformed one and another tenant's grant are the same 404.

account_admin dashboard login only (see 403).

Parameters

NameInTypeRequiredDescription
idpathstring (uuid)yes

Responses

200 ​

The grant, now revoked.

FieldTypeRequiredDescription
grantobject (SupportAccessGrant)yes
grant.idstring (uuid)yes
grant.granted_bystring (uuid)yesThe account_admin who granted it.
grant.reasonstringyes
grant.duration_minutesintegeryes
grant.created_atstring (date-time)yes
grant.expires_atstring (date-time)yesAlways created_at + duration_minutes; it never moves.
grant.revoked_atstring (date-time) | nullyes
grant.revoked_bystring (uuid) | nullyes

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 ​

role_forbidden: anything but a dashboard login (ddu_) whose person has the account_admin role.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "revoking platform-support access takes a dashboard login with the account_admin role"
  }
}

404 — NotFound ​

Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).

json
{
  "error": {
    "code": "not_found",
    "message": "resource not found for this tenant"
  }
}

409 ​

grant_already_revoked: the grant was revoked before.

json
{
  "error": {
    "code": "grant_already_revoked",
    "message": "this grant was already revoked"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

The trail of platform-support access, newest first ​

http
GET /v1/support-access/events

The append-only record of the consent and of everything support did under it, keyset-paginated, newest first. event is one of grant_created, grant_revoked, session_opened, session_revoked, request and request_denied. A request row names the route PATTERN and the id in the path, never a body, a query string or a response. Rows are never edited or deleted.

Filters: event, grant_id, since (inclusive) and until (exclusive), both RFC-3339 on occurred_at. A filter that does not parse is 400 naming the accepted values — never a silently empty page. Tenant-scoped: another tenant's rows are never visible, whatever the filter.

account_admin dashboard login only (see 403).

Parameters

NameInTypeRequiredDescription
eventquerygrant_created · grant_revoked · session_opened · session_revoked · request · request_deniedno
grant_idquerystring (uuid)no
sincequerystring (date-time)noOnly events at or after this instant.
untilquerystring (date-time)noOnly events strictly before this instant.
limitqueryintegerno
cursorquerystringnoOpaque keyset cursor — the next_cursor of the previous page.

Responses

200 ​

One page of the trail, newest first.

FieldTypeRequiredDescription
eventsarray of SupportAccessEventyes
events[].idstring (uuid)yes
events[].grant_idstring (uuid)yes
events[].session_idstring (uuid) | nullyesThe support session; null on grant_created and grant_revoked.
events[].eventgrant_created · grant_revoked · session_opened · session_revoked · request · request_deniedyes
events[].actor_kindcustomer_user · operatoryes
events[].actor_refstringyesThe customer person's id, or operator:<id> (<name>) for platform staff.
events[].methodGET · HEAD | nullyesOnly on request and request_denied.
events[].routestring | nullyesThe route PATTERN (for example /v1/leads/:id), never the path with values; only on request and request_denied.
events[].resource_idstring | nullyesThe id in the path, on request rows only.
events[].ipstring | nullyes
events[].occurred_atstring (date-time)yes
next_cursorstring | nullyesCursor of the next page; null on the last one.

400 — BadRequest ​

Malformed request (shape/type errors, invalid filters, bad timestamps).

json
{
  "error": {
    "code": "bad_request",
    "message": "from must be RFC-3339"
  }
}

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 ​

role_forbidden: anything but a dashboard login (ddu_) whose person has the account_admin role.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "the support-access trail takes a dashboard login with the account_admin role"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Whether a support session is open right now (the banner) ​

http
GET /v1/support-access/active

What the dashboard shows every person of the tenant: is someone from platform support inside the account at this moment, until when, and who. active is false when the support_access_enabled flag is off for the tenant, when there is no live grant, or when no session of it is open and unexpired. A session is open from its session_opened event until a session_revoked one, for at most 60 minutes and never past the grant's expiry. The support session itself arrives with a later change: until then this answers active: false for every tenant.

Any dashboard login (ddu_) of the tenant, member included. An API key (dd_) is 403.

Responses

200 ​

The banner's answer.

FieldTypeRequiredDescription
activebooleanyes
expires_atstring (date-time) | nullyesWhen the open session ends; null when not active.
operator_namestring | nullyesThe platform person's name; null when not active or unnamed.

401 — Unauthorized ​

Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.

json
{
  "error": {
    "code": "unauthorized",
    "message": "invalid or revoked API key"
  }
}

403 ​

role_forbidden: a dd_ API key; forbidden: a widget token or a suspended tenant.

json
{
  "error": {
    "code": "role_forbidden",
    "message": "the support-access banner takes a dashboard login"
  }
}

429 — ApiKeyRateLimited ​

This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.

json
{
  "error": {
    "code": "rate_limited",
    "message": "API key request quota exceeded; retry after the interval in Retry-After"
  }
}

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.