AACsearch
Security & Compliance

API keys

API key types, scopes, storage model, rotation, and revocation.

API keys

AACsearch issues three families of API key. They all share one storage rule: only the SHA-256 hash of the key is stored in our database. The raw key is shown exactly once at creation. If you lose it, you create a new key — we cannot recover the old one.

Key families

PrefixFamilyWhere to use
ss_search_…Search / ingest / adminServer-side. Issued from the dashboard. Hashed at rest.
ss_connector_…CMS connector keyServer-side, by the CMS module. Same hash space as ss_search_….
ss_scoped_…Browser-safe tokenClient-side. Short TTL. HMAC-signed, not stored. See Scoped tokens.

ss_connector_* keys exist so a CMS plug-in can rotate independently of the merchant's dashboard key. They hash to the same slot as ss_search_* keys with the same body — they are operationally distinct, not cryptographically distinct.

Scopes

Every persisted key carries one or more scopes:

ScopeWhat it grants
searchRead-only access to search and suggest endpoints.
ingestUpload, update, and delete documents in your indexes.
adminIndex management — create, configure, reindex, delete.
connector_writeReserved for CMS connectors. Index-scoped writes.
scim_adminProvisioning via the SCIM 2.0 endpoint. See SSO and SCIM.

Scopes are checked on every request. A search key calling an ingest endpoint returns 403. A scope you did not grant is a scope you do not have.

Never put an ingest, admin, or scim_admin key in browser code. Browser keys must be either an origin-restricted search key with a tight rate limit, or a scoped token.

Storage model

When you create a key, the server:

  1. Generates 32 random bytes and base64url-encodes them.
  2. Prepends the family prefix (ss_search_, ss_connector_, …).
  3. Stores sha256(rawKey) (hex) in the search_api_key.hash column.
  4. Stores the first 12 characters as prefix so the dashboard can show e.g. ss_search_AB… in the list.
  5. Returns the raw key in the response once.

The raw key is never logged, never written to disk in plaintext, and never leaves the database row that contains its hash. If our database is compromised, the attacker still cannot use your keys against the API — they only have hashes.

Verification is constant-time (crypto.timingSafeEqual) so the API does not leak information about which keys exist.

Creating a key

From the dashboard:

  1. Open Project → API keys.
  2. Click Create key.
  3. Name the key after the system that will use it (e.g. frontend-search-eu, ingest-worker-prod).
  4. Pick the scopes — fewer is better.
  5. Optional: restrict to specific indexes.
  6. Optional: set an Origin allow-list. See Origin allow-list.
  7. Optional: set a per-minute rate limit.
  8. Optional: set an expiry date.
  9. Click Create, copy the key once, and store it in your secret manager.

Programmatically, the same is exposed via POST /api/orpc/searchApiKey.create.

Rotation

Rotate any key with admin scope at least every 90 days. Browser-facing keys can rotate more often.

Recommended procedure (zero downtime):

  1. Create a new key with identical scopes and origin restrictions.
  2. Deploy the new key to your application config (environment variable, secret manager). Keep the old key in place.
  3. Verify in the dashboard's "Last used" column that the new key is receiving traffic and the old one has gone quiet.
  4. Revoke the old key.

If a key may be compromised, skip the gradual procedure and go straight to Revocation.

Revocation

Revoking a key flips revokedAt on the row. The next verification fails with invalid_or_revoked_key (HTTP 401). There is no grace period and no in-memory cache to wait out.

From the dashboard, Project → API keys → Revoke. Programmatically, POST /api/orpc/searchApiKey.revoke.

If a key leaks

  1. Revoke it immediately. Do not wait to investigate first.
  2. Issue a replacement with the same scopes and deploy it.
  3. Review the audit log. See Audit logs. Filter by the key's prefix to find unexpected callers.
  4. Scan analytics for query patterns that don't look like your application.
  5. Reindex any data that an admin or ingest key could have tampered with.

Common mistakes

  • Committing keys to Git. Add ss_search_, ss_connector_, and ss_scoped_ to your secret-scanning rules.
  • Reusing one admin key everywhere. A single leak compromises every environment. Use one key per service per environment.
  • Putting admin scope in browser code. The browser key should be search-only with an origin allow-list, or a scoped token. Anything else gives every visitor of your site the ability to mutate your data.
  • Forgetting the origin allow-list. Without it, an attacker who copies the key from your JS bundle can call your search API from anywhere. See Origin allow-list.

See also

On this page