Skip to main content
GET /api/v1/accounts/{account_id}/contacts/{contact_id}/consent returns the existing Consent::Report for one contact. It requires an authenticated account administrator. The account is taken from the authenticated request and the contact is looked up within that account. There is no Studio feature gate. The account API accepts its supported signed-in user and account API token authentication methods; send one authentication method per request. The inherited account login-block decision controls ordinary account reads. An open suspension with login blocking enabled can return 401 even when the account status is active. An open suspension can also explicitly permit ordinary reads while the account status remains suspended. When no open suspension controls the decision, the account status supplies the fallback, so a suspended account returns 401. An authorized own-account read-only staff impersonation can still read this report for a suspended account. Normal account membership, administrator authorization, and contact scoping still apply. A trial-locked account can also read this report. The request has no query parameters. Extra request values do not filter or change the report. A successful read does not write the contact or consent ledger. It emits one structured log entry containing user, account and contact IDs plus generated_at; there is no durable audit row for this read.

Reading the report

The response includes contact_id, generated_at, history_limit (currently 100), merged_contact_ids, legacy_opt_outs, and inboxes. An empty inboxes array means no ledger events were found. It is not proof of consent or that all sends are allowed. legacy_opt_outs separately lists AMB and RCS inbox IDs that their existing legacy readers report as opted out; it has no SMS key. Each returned inbox has its channel and both purpose entries. state_now is the existing ledger policy answer at generated_at, with an allowed boolean and a reason string. The reason vocabulary can expand. A transactional allowed: true means opt-in is not required for that transactional purpose; it is not recorded consent. Current deployment enforcement varies by channel and send path, so this report does not guarantee that a send is allowed or blocked. Each purpose has at most 100 newest history events and a truncated flag. Events dated in the future can appear in history but do not decide the current state. A voided event remains visible with voided: true, while active events omit that key. evidence_event_id and evidence_event identify the policy’s deciding event separately from the bounded history, so the deciding event may be older than the returned 100 rows. Merged ledger events retain their original captured_contact_id; the report lists those prior IDs in merged_contact_ids. Event entries include ledger IDs, state, purpose, mechanism, actor and source references, effective and recorded times, and allowlisted evidence identifiers. Only string values of disclosure_version, disclosure_sha256, and form_ref are returned from evidence. Stored message text and other evidence keys are omitted. source_ref, form_ref, and metadata originate in source data and should be treated as untrusted values.

Current surface and requested gaps

This endpoint supplies only a partial read for the requested consent and outreach surface. The following actions are proposals only; there are no callable routes or operation IDs for them. Their implementation and exposure decisions belong to ENG-5898. “Read” is a proposed risk class, not approval to expose an operation. No proposal here is approved for activation. All future writes remain risky pending approval. Proposed future behavior includes append-only consent history that must not be deleted, immediate no-send behavior after a recorded withdrawal, repeat-safe withdrawals, and enforcement before sends; failed imports must not write rejected rows. importPreview is proposed as a dry run with no contact, ledger or outbound effects. Proposed event payloads contain IDs and counts only, never contact content. These requirements remain unimplemented and unverified. Proposed event names are consent.recorded, consent.withdrawn, consent.imported, consent.capture_method_changed, outreach.enforcement_changed, outreach.held_resolved, and outreach.export_ready; delivery is not claimed. Proposal metadata remains unapproved, MCP-hidden and SDK-excluded pending the ENG-5898 decisions.

Responses

  • 200: report for the account-owned contact.
  • 400: conflicting credential methods were supplied.
  • 401: missing or invalid authentication, non-member account access, or an ended impersonation session.
  • 403: non-administrator role or a structured impersonation refusal.
  • 404: contact does not exist in the authenticated account.