Guides / Support Cases
Support Cases and customer feedback.
Accept Project-scoped questions, feedback, and bug reports; preserve public and internal correspondence; and operate an audited Tenant-wide inbox.
One resource, two views
The same /v1/support/cases resources serve two deliberately different principals. A Tenant Member with the owner, admin, or support role sees the Tenant-wide operator inbox, internal notes, and external references. A service credential sees only cases in its exact Project and only public messages and events. It needs support_cases.write to create or append and support_cases.read to retrieve.
This does not require a dedicated Support SDK. The surface is ordinary JSON, multipart upload, cursor pagination, and ETag concurrency; generated clients may come from the canonical OpenAPI document. A customer BFF should expose only the small browser-safe product flow it actually needs.
Create from a customer backend
const response = await fetch(
COMPLICATEDAUTH_URL + "/v1/support/cases",
{
method: "POST",
headers: {
Authorization: "Bearer " + SERVICE_CREDENTIAL,
"Content-Type": "application/json",
"Idempotency-Key": logicalSubmissionId,
},
body: JSON.stringify({
category: "bug",
subject: "Checkout returned to sign-in",
message: "The redirect happened after payment confirmation.",
reporter_project_user_uid: projectUserUid,
diagnostic_consent: true,
diagnostics: {
application_version: "2026.08.23",
platform: "web",
current_url: "https://app.example.com/checkout",
request_id: requestId,
occurred_at: new Date().toISOString(),
},
}),
},
);The credential’s Project is authoritative; a caller cannot select another Project. The optional reporter must be a Project User in that same Project. Diagnostics use a fixed allowlist and are rejected unless diagnostic_consent is true. Current URLs cannot carry credentials, queries, or fragments.
Categories, status, and priority
| Dimension | Values | Owner |
|---|---|---|
| Category | bug, feedback, question | Customer intent; operators may correct it. |
| Status | open, in_progress, waiting_for_customer, resolved, closed | Operator workflow; a customer backend may only close or reopen to open. |
| Priority | low, normal, high, urgent | Operator triage only; customer urgency does not silently become authority. |
Updates require the latest strong ETag in If-Match. Invalid transitions return 409 invalid_status_transition; stale updates return 412 version_conflict. Resolved and closed cases reject new content until explicitly reopened.
Messages and internal notes
Messages are append-only. Operators choose public or internal; service credentials can create and read only public correspondence. A customer backend may attribute a public message to a same-Project user while the security audit still records the actual service account that submitted it. Every message creation requires an idempotency key and supports exact 24-hour replay.
Attachments
Upload one multipart file part. PNG, JPEG, WebP, PDF, UTF-8 text, and valid JSON are accepted; active-content and opaque binary formats are rejected. Each file is limited to 5 MiB, with at most 20 files and 25 MiB per case. Metadata and bytes are encrypted separately. Downloads always use private no-store caching, attachment disposition, and nosniff; applications must still treat customer files as untrusted.
const body = new FormData();
body.set("file", file);
body.set("uploader_project_user_uid", projectUserUid);
await fetch(
COMPLICATEDAUTH_URL + "/v1/support/cases/" + caseUid + "/attachments",
{
method: "POST",
headers: {
Authorization: "Bearer " + SERVICE_CREDENTIAL,
"Idempotency-Key": logicalUploadId,
},
body,
},
);External references
Tenant operators can link a case to a generic provider and external identifier, with an optional safe URL and label. These fields are encrypted, and their values never enter case-event or audit payloads. Project service credentials cannot read them. A connector should use this resource for correlation rather than adding provider-specific columns or treating a remote status as authoritative.
Encryption, events, and retention
- Subjects, diagnostics, message bodies, filenames, attachment bytes, external IDs, URLs, and labels use versioned AES-256-GCM encryption with Tenant, case, field, and child identifiers as authenticated context.
- Category, status, priority, assignment, counts, timestamps, and opaque IDs remain queryable for the inbox.
- Case events are append-only. Service credentials receive public events; operators also receive internal routing facts. Events never contain encrypted content.
- Security audit events record the actual principal, Project, operation, and safe target metadata. They omit message bodies, filenames, file bytes, and external identifiers.
- Closing a case sets
retention_untilto 365 days later and schedules a deduplicated purge job. Reopening clears the case deadline. PostgreSQL-leased workers use bounded retry and re-check the locked case state before deletion, so an old job cannot purge a reopened case.
Retries and limits
Case, message, attachment, and external-reference creation require Idempotency-Key. Reuse the original key only with the identical logical request after a timeout or network failure. Changed inputs conflict. List messages and events in ascending order, other collections in reverse updated or creation order, and pass opaque cursors back unchanged. A Tenant may retain at most 10,000 non-closed cases; each case permits 500 messages and 20 external references.