Skip to main content

Ingestion keys

An ingestion key is the credential an external system uses to push documents into a search index — a CMS sync, an ETL job, a nightly export. It is a server-side secret that can write and delete.

It is not the same thing as an access token, and the two are not interchangeable. If you have been handed a token and are trying to upload with it, you want a key instead.

Ingestion key or access token?

Access tokenIngestion key
Used byBrowsers, widgets, frontend appsServers, sync jobs, CI
Can it be seen by the public?Yes — by design. It is baked into the embed snippet and readable from the page source.No. Never put one in a browser.
What it can doSearch, autocomplete, read one documentAdd, update, and delete documents
Scoped toOne experienceSpecific indexes + specific operations
Stored asPlain value on the experienceHashed. Shown once, never recoverable.
RevocableBy rotating the tokenYes, immediately
HeaderX-Access-Token or Authorization: BearerAuthorization: Bearer only

The distinction is the whole point: an access token is safe to publish because it can only read. Writes and deletes need a credential that is genuinely secret, which is why they get their own type.

Creating a key

Sidebar → Capabilities → Search Indexes → open your index → Ingestion Keys card.

Give it a name you'll recognise later ("Storyblok sink (EN)") and tick the permissions it needs:

  • Write — add and update documents
  • Delete — remove documents

Grant only what the integration actually does. A sync that only ever pushes content does not need delete.

The key is shown once, on creation:

ik_a1b2c3d4e5f6_xK9mP2qR7sT4vW8yZ1aB3cD5eF7gH9jK2mN4pQ6rS8t

Copy it immediately. It is stored as a hash, so there is no way to look it up later — a lost key has to be revoked and replaced. After creation the list shows only the ik_… prefix, which is enough to tell keys apart.

Using a key

Send it as a bearer token:

curl -X POST "$INTERAKT_URL/api/search-indexes/$INDEX_ID/documents" \
-H "Authorization: Bearer $INTERAKT_INGESTION_KEY" \
-H 'Content-Type: application/json' \
-d '{"documents": [{"id": "PROD-001", "name": "Pacific runner sneaker"}]}'

The same header works on every document endpoint — see Loading data for the full list.

Reading is always allowed for an index the key is scoped to, so a sync can verify its own writes without extra permissions.

What happens when a key isn't allowed

ResponseMeaning
401Missing, malformed, unknown, revoked, or expired key
403Valid key, but not permitted here — wrong index, or missing the operation

The messages are deliberately vague. These endpoints are internet-facing, and a precise error ("no such index") would let someone map your setup by guessing.

Rotating and revoking

Revoke from the same card. It takes effect immediately, with no redeploy — the next request from that key gets a 401.

To rotate without downtime: create the new key, deploy it to the integration, confirm traffic is flowing, then revoke the old one. Both work at once, so there is no gap.

Revoke straight away if a key is committed to a repository, pasted into a ticket, or sent over chat.

Auditing

Every upload records which key made it. The batch history on the index shows uploads from the admin UI and from each key separately, and lastUsedAt on the card tells you whether a key is still in use — handy for finding keys you can safely revoke.

Good practice

  • One key per integration. Shared keys can't be revoked without breaking every consumer.
  • Least privilege. Skip delete unless the integration genuinely deletes.
  • Environment variables, not code. Never commit a key.
  • One key per environment. Staging should not hold a key that reaches production data.

Common gotchas

  • Using an access token to upload. Returns 401. Access tokens cannot write — that is deliberate, not a bug.
  • X-Access-Token with an ingestion key. Not read on these endpoints; use Authorization: Bearer. The headers are kept separate so a public token can never be mistaken for a secret one.
  • A key scoped to the wrong index. Returns 403. A key only reaches the indexes it was granted.
  • Expecting to see the key again. You can't. Only the prefix is retained.
  • Browser calls with an ingestion key. These endpoints send no CORS headers, because a secret key has no business being in a page.

Where to go next