Skip to main content
This page describes security behavior you can observe and verify through the product and the API. It documents what the platform does — not aspirations.

API key handling

  • A key’s secret is generated from 32 random bytes and shown once, when the key is created or rotated. Cactal stores only its SHA-256 hash; nothing on Cactal’s side can reveal the plaintext again.
  • Rotation (POST /v1/apiKeys/{apiKeyId}/rotate) replaces the secret immediately — the old secret stops authenticating the moment the call returns.
  • Revocation (POST /v1/apiKeys/{apiKeyId}/revoke) is permanent. Keys can also carry an expiresAt timestamp; expired and revoked keys fail authentication with 401.
  • REST routes on api.cactal.ai accept bearer API keys. The MCP route also accepts audience-bound OAuth access tokens. Requests carrying a browser session cookie are rejected with 401, so bearer credentials and sessions can never be confused on the public surface.
See Authentication for the full key lifecycle.

Access control

Authorization is capability-based. Roles expand to capability sets, and every operation — over the dashboard, REST, or MCP — checks one required capability before running.
  • Organization roles: owner, admin, full_editor, content_editor.
  • Website-scoped roles: full_editor, content_editor, granted per website.
  • API keys carry the same model: organization-scoped or website-scoped access with a role. The owner role is not grantable to keys.
The MCP server applies the same checks and additionally filters tools/list, so a user or key never sees tools it cannot call. See Access control.

MCP OAuth

  • Standard protected-resource and authorization-server metadata let MCP clients discover the authorization flow without Cactal-specific configuration.
  • Cactal accepts Client ID Metadata Documents from public HTTPS origins. Client metadata may link to a public homepage on another HTTPS origin, while non-loopback redirect URIs remain bound to the metadata document’s origin. The consent screen identifies discovered clients by that fetched origin. Verified publisher branding is reserved for identities Cactal recognizes, including ChatGPT.
  • Clients that use dynamic registration, including Claude’s current connector flow, remain visibly unverified because their publisher details are self-reported. All public clients use the authorization code flow with PKCE; client-credentials grants are not exposed.
  • Access tokens are signed JWTs with the Cactal authorization server as issuer and https://api.cactal.ai/v1/mcp as their exact audience. Tokens without the mcp scope are rejected.
  • Consent is explicit, refresh tokens require offline_access, and tool calls retain the signed-in user’s grants and audit identity.

Tenant isolation

A website you cannot access returns 404 not_found, the same response as a website that does not exist, so website existence never leaks across organizations or across website scopes.
This applies to websites and everything scoped under them: source code, CMS, assets, domains, and analytics. Organization-scoped endpoints (members, invitations, the audit log, and billing) behave differently: a caller without a grant on an organization that exists gets 403 forbidden, and 404 not_found means the organization id itself is unknown.

Audit logging

Lifecycle and access-control operations write audit events to the organization’s audit log, whichever interface invoked them: website create/rename/delete/restore/transfer, editor grants and invitations, API keys, domains, organization members and invitations, billing, and analytics CSV export downloads. Source file edits, publishes, CMS content, and asset operations are not audit-logged — review content work through source versions, changed-file lists, and targeted diffs instead. Each audit event records:
  • The event type and a human-readable summary.
  • The affected resource type and id.
  • Actor attribution: actorKind distinguishes a user from an api_key, with a reference to the specific user or key.
Query the log with GET /v1/organizations/{organizationId}/audit-log, filtering by actorKind, eventType, resourceType, actorPrincipalId, or websiteId (which matches both the website itself and resources belonging to it). Agent-safe publishing shows how to use this for agent oversight.

Draft privacy

Draft and version preview hosts (draft--<label> and v<n>--<label> on cactal.app) are served with:
Search engines are instructed not to index preview content. Preview URLs are reachable by whoever holds the link, so share them deliberately.

Separated planes

The control plane and customer content run on different domains: Browser sessions exist only on the control plane. Customer-authored content — pages, scripts, assets — is served from the content plane, never from a control-plane origin. See Domains.

Publish build isolation

Publishing builds a website’s source in a disposable sandbox created for that job and discarded afterwards. A deployment serves traffic only after its build reaches ready; a failed build does not replace what is currently serving. See Publishing.

Abuse mitigation

The API rate-limits at several layers. Beyond the per-key buckets, two per-IP buckets protect the authentication surface itself:
  • A pre-authentication bucket caps total requests per IP before any key is checked.
  • An authentication-failure bucket allows a burst of 25 failed attempts per IP, refilling at 0.5 per second — throttling key guessing to a crawl.
Exact bucket sizes and 429 semantics are in Rate limits.

Sign-in

Human sign-in uses Google or email with a password or a one-time code (OTP) sent to that address. The public API host exposes no sign-in routes; MCP OAuth sends users to the product app’s authorization server, while REST access uses API keys.

Reporting

Report suspected vulnerabilities or security incidents to support@cactal.ai. Include reproduction details; do not include API key secrets in the report.