Compliance
You are the data controller. Most processing runs in your environment.
Shipped GDPR and SOC 2 controls. See also Security, Data boundary, and the Anyray compliance page for the controller/processor split.
As of 6 July 2026, examined by Securance Pro Assurance PLLC (Security, Availability, Confidentiality). Type II in progress. Report under NDA from security@anyray.ai. Anyray supplies control inventory and evidence for your own deployment's attestation.
What data is stored
All on your volumes except the last two rows, which never carry content.
| Store | Data | Where |
|---|---|---|
| Spend store | Per-request metadata: user/team, model, provider, tokens, cost, latency. Never content. | Your Postgres (anyray_spend) |
| Connector observations (when configured) | Vendor usage metadata: timestamps, user/model ids, vendor-native quantities, reported cost. Never prompts, responses, source code, or attachments. Kept separate from request spend. | Your Postgres (anyray_spend_connector_events) |
| Observability traces | Attribution and token/cost/latency metadata. Content only per the content mode. Per-session hourly rollups (counts, tokens, latency, user, model, client tool, provider and effort ids) back the Sessions list. | Your Postgres (anyray_traces / anyray_observations; rollups in anyray_trace_session_hours) |
| Client keys & enrollment links | Usernames bound to records, token hashes only (SHA-256). | Gateway data volume |
| Optimizer ephemeral stores | Semantic-cache responses in memory; stashed originals as AES-256-GCM blobs, with a short-lived per-replica cache the database outranks. TTL-bounded, keyed by content-free hashes. | Optimizer memory; your Postgres (anyray_context_stash) |
| Audit logs | Admin/GDPR actions and optimizer settings changes: actor, action, timestamp. | Your Postgres (anyray_audit_log); per-pod files as fallback |
| Anyray Billing App plane | Operator emails/names (Google sign-in), tenant memberships, content-free usage rollups, seat hashes, and the deployment heartbeat (what crosses). | Vendor Billing backend |
| Support bundles | Only when an admin clicks Send: the redacted, content-free diagnostic bundle plus its analysis. Never automatic, never pullable by Anyray. | Vendor Billing backend |
Data subject requests
Needs security_admin or owner (RBAC) or
ANYRAY_ADMIN_TOKEN. Every call is audit-logged.
Erasure (GDPR Art. 17)
| Store | Endpoint | Removes |
|---|---|---|
| Gateway | delete /admin/gdpr/users/:user | The user's spend rows, cap and budget counters, client keys and enrollment links (cutting their access), trace rows, Sessions rollup rows naming them, and their context-stash rows, including rows an agent with delegated attribution stashed for them. The erase advances the stash's write fence, so no replica writes an erased row back from cache. An owner-level request also removes connector observations whose attribution.user, userId or agent matches (and refuses a re-import of them, in any letter case, for 90 days), the user's per-user overrides in the team policy (hook switches, billing lanes, subscription fallback), the auth failures recorded under their address, their connect-health seats (with the time zone Anyray Connect reported) and check results, the connect directives queued for them, and their machine-to-seat rows. Any request also removes the user's connect-hook trim records in its tenant, and releases the files, batches, fine-tuning jobs and stored responses they own on the organization's provider keys: each keeps a placeholder record with no identity, so only an admin can reach it. Agents the user owns keep running with no owner, so whoever holds the address next sees none of their traffic; unassignedAgentKeys counts them. Returns per-store counts; erasedContextStash is null, not 0, when that store was unreachable, and erasedPolicyOverrides, erasedAuthEvents, erasedConnectHealth, erasedConnectDirectives and erasedMachineSeats are null on a tenant-scoped request, which lists those stores as excluded. erasedDeviceTokens is always 0: the field stays so older clients keep parsing the response. Once the erase returns, no gateway replica shows the user in a console view, and every replica drops the user from its in-memory caches (at once on the one that served the request, within a minute on the others). A team the identity provider's directory assigns the user comes back on the next directory sync while the directory still lists them. A failed connector, key-store, policy, auth-failure, connect-health or seat step fails the erase, and so does an audit row the other replicas cannot read. If the user's seats cannot be looked up or recorded, the erase stops before deleting anything. After any later failure, a crash included, a retry within 7 days still reaches every seat the first attempt found. A connect-health report from before the erase that reaches the database after it is refused, so no replica brings the user's seats back. A budget alert about the user still waiting for the next metering round is dropped the same way. Every store matches the address regardless of letter case (ASCII letters only). |
| Optimizer | post /admin/optimizer/purge | The replica taking the call clears its in-memory caches at once, and shared stash rows are gone for every replica. Other replicas with the shared store drop their caches within 30 seconds (2 minutes at most if reads fail). A replica awaiting the gateway's database push clears on its first read after the push; one never given a database is outside the receipt's coverage. Without a confirmed shared fence, the receipt reports not_signalled. |
| Anyray Billing App | delete /portal/me | A Billing App operator's account, memberships, and sessions. Returns 409 if they are a tenant's sole owner. |
Access & portability (GDPR Art. 20)
| Scope | Endpoint | Returns |
|---|---|---|
| Gateway | get /admin/gdpr/users/:user/export | JSON of the user's spend rows, key/link metadata (hashes stripped) and, for an owner-level request, the connector observations the erase removes. In those, any other person's id, such as someone the user filed a row for, reads [another person]. A request not scoped to a tenant also returns the user's own per-user overrides in the team policy (teamPolicyOverrides, one per map, and teamPolicyOverridesBySpelling, every entry the erase removes), with fallback key ids left out. Content is stripped even in plaintext. A tenant-scoped request returns teamPolicyOverrides: null and lists the policy as excluded. An owner-level request also returns the time zone on each connect-health seat the erase removes as connectSeatTimeZones ([] when none holds one, null on a tenant-scoped request). A policy or seat read that fails fails the export. It matches the user the same way the erase does. It also lists the provider files, batches, fine-tuning jobs and stored responses the user owns on the organization's provider keys (providerResources: provider, kind, id and creation time). The spend rows, connector observations and those records stream as they are read, so a long history is never held in gateway memory. If the store fails partway through, the response stops before the JSON closes. Run the export again. |
| Anyray Billing App | get /portal/me/export | A Billing App operator's profile, memberships, and audit-log entries. |
Retention (GDPR Art. 5(1)(e))
| Store | Default | Configurable |
|---|---|---|
| Spend store | Kept indefinitely unless ANYRAY_SPEND_RETENTION_DAYS is set (prunes at boot and daily) | Yes |
| Connector observations | 90 days max; a shorter positive ANYRAY_SPEND_RETENTION_DAYS wins. Cleanup runs without active connectors. | No |
| Observability traces | Console Privacy page, 1–3650 days; unset follows ANYRAY_SPEND_RETENTION_DAYS, else 90 days. Never indefinite. | Yes |
| Optimizer cache / stash | Cache in memory, 3600s, lost on restart. Stash 4h, +30m per retrieval, 48h ceiling (details); swept hourly | No |
| Optimizer decision pins | Pruned after 30d idle | No |
| Deployment health | Samples and replica runs 7d; problem log 90d after a problem closes; pruned hourly | No |
| Audit logs | Kept indefinitely in Postgres (spend retention does not touch them); fallback files until rotated | Via your Postgres retention |
| Billing App plane | Sessions expire; invites pruned after 90d; usage rollups tiered (30d / 90d / 365d), plus undatable rows and future-dated rows nothing has reported for a full window | ANYRAY_CP_RETENTION_TIERS |
| Support bundles (vendor side) | Newest 20 per deployment, 90d | No |
Audit trails (SOC 2 CC6/CC7)
Every admin mutation is an append-only, metadata-only event: { ts, actor, action } plus a
category. actor is the IdP email, break-glass for the admin key, or unknown for anything not
identity-shaped. Gateway and optimizer trails go to anyray_audit_log in the shared Postgres; per-pod
files under ANYRAY_DATA_DIR are the fallback.
Covered: GDPR/DSAR operations, content mode, client/provider keys and enrollment links, user caps, routing and model aliases, optimizer config and purges, admin sessions, Billing App lifecycle. Access controls: Security (console SSO with RBAC).
A GDPR export that stops before its JSON closes adds an audit row with spendRecords and
connectorRecords at -1. That later row wins over any earlier row for the same export.