---
title: Keys and scopes
description: The two kinds of key, how a key is scoped to one environment, and how scopes and rotation work.
type: reference
product: platform
summary: Secret and publishable keys, scoped to one environment and a set of scopes.
updated: 2026-09-28
order: 3
---

An API key authenticates a call. It has a kind, a mode and a list of scopes, and
it is scoped to one organization, one project and one environment.

A key made for the organization as a whole is scoped to the organization alone:
it has no project and no environment.

## The two kinds of key

A **secret** key begins `sylphx_sk_live_` or `sylphx_sk_test_`, followed by a
20-character id, a 32-character secret and a 6-character checksum. It
authenticates a call from a server, and you keep it there.

A **publishable** key begins `sylphx_pk_live_` or `sylphx_pk_test_`, followed by
an id. It carries no secret, and it is safe in a browser or a mobile app because
it can hold only four scopes:

- `auth:public`: read the environment's public auth configuration.
- `events:realtime:subscribe`: subscribe to realtime events.
- `observability:ingest`: send observability data, such as errors captured in a
  browser.
- `config:evaluate`: read feature flag values for one user or device, never the
  rules behind them.

It cannot read or write your resources.

<Callout tone="danger" title="Never ship a secret key">
	Never put a secret key in client-side code. A browser or a mobile app gets a
	publishable key, which cannot read or write your resources even if someone
	reads it out of the bundle.
</Callout>

## What a key carries

<PropertyTable
	properties={[
		{
			name: 'kind',
			type: 'string',
			description: 'Whether the key is a secret key or a publishable key.',
		},
		{
			name: 'mode',
			type: 'string',
			description:
				'`live` or `test`, fixed in the key string when the key is made. A key may act only on an environment in the same mode.',
		},
		{
			name: 'scopes',
			type: 'string[]',
			description: 'What the key may do, each scope naming a service and an action.',
		},
		{
			name: 'expire_time',
			type: 'string',
			description: 'The time after which the key stops working, when you set one.',
		},
	]}
/>

A key also carries whether it has been revoked, and why. A reason is one of
`user`, `rotation`, `leaked`, `claimed`, `claim_expired`, `org_deleted`,
`admin`, `role_changed` or `logout`.

## Scopes

A scope names a service and an action, joined by a colon
(&lt;service&gt;:&lt;action&gt;), so `hosting:read` reads a hosting resource and
`hosting:write` changes one.

Every valid key holds `access:whoami` implicitly, whatever else it carries. You
never have to grant it, and it cannot be granted: a create that asks for it is
refused.

These are examples of the form, not the complete set: `hosting:read`,
`hosting:write`, `data:read`, `keys:use`, `secrets:write`, `events:write`,
`billing:read`, `access:members:write`, `access:admin`. A key may also hold the
wildcards `*:read` and `*:write`, which cover every service the project has
enabled.

A key can never hold a scope its creator does not hold: the scopes you ask for
must be a subset of your own.

## Creating a key

A key lives under an environment, so `--parent` names the environment it belongs
to.

<CodeBlock
	language="bash"
	code={`sylphx access api-keys create \\
  --parent orgs/{org}/projects/{project}/envs/production \\
  --spec.kind secret \\
  --spec.label ci \\
  --spec.scopes hosting:read,data:read`}
/>

Add `--dry-run` to see what would be created without creating it. The commands
you use by hand are `sylphx access api-keys create`, `sylphx access api-keys
revoke` and `sylphx access api-keys roll`.

The secret is returned once, when the key is created or rolled. After that the
key reports its id and the last four characters of the secret, and nothing more.

## Rolling a key

Rolling mints a new secret with the same scopes, and the old key keeps working
until the grace period ends.

<CodeBlock
	language="bash"
	code={`sylphx access api-keys roll \\
  orgs/{org}/projects/{project}/envs/{env}/api_keys/{api_key}`}
/>

The grace period is 24 hours by default, and you can set it with
`--grace-period`; anything longer than 7 days is refused. A key that a secret
scanner reports as leaked is not rolled at all: it is revoked at once, so the
old secret stops working immediately.

## Sending a key

Send a key as the `Authorization` header: the word `Bearer`, a space, then the
key.

<CodeBlock
	language="bash"
	code={`curl https://api.sylphx.com/v1/whoami \\
  -H "Authorization: Bearer $SYLPHX_API_KEY"`}
/>

## A new key takes a moment

A brand-new key can take a moment to work. A call made in that moment fails with
`NOT_YET_PROPAGATED`, which is retryable: send the call again. [Errors](/docs/platform/errors)
covers that code and the rest.

<RelatedDocs
	links={[
		{ href: '/docs/platform/errors', label: 'Errors' },
		{ href: '/docs/platform/environments', label: 'Environments' },
		{ href: '/docs/platform/billing-and-limits', label: 'Plans, usage and limits' },
	]}
/>
