ComplicatedAuth
Menu

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.

Standalone boundary: Support Cases are native ComplicatedAuth resources. A future connector may synchronize them through generic external references, but no external product owns their identity, status, authorization, or retention.

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

DimensionValuesOwner
Categorybug, feedback, questionCustomer intent; operators may correct it.
Statusopen, in_progress, waiting_for_customer, resolved, closedOperator workflow; a customer backend may only close or reopen to open.
Prioritylow, normal, high, urgentOperator 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_until to 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.

Search guides, architecture, SDKs, and the REST API.