> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeroclick.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Keys and secrets

> The credentials a ZeroClick integration runs on: the signing secret and its kid, scoped usage keys, admin keys, rotation, and vault-backed resolution.

Your integration runs on three runtime credentials, all minted in the [dashboard](https://dashboard.zeroclick.io) under your store's **Implementation** tab (or **Settings** for individual keys):

| Credential      | Format                                             | Used for                                                 |
| --------------- | -------------------------------------------------- | -------------------------------------------------------- |
| Signing secret  | Value `zcsec_` + 48 chars, key id (`kid`) `hsec_…` | Verifying the `zc-signature` on every forwarded request. |
| Usage read key  | `zc_` + 48 chars, scope `usage:read`               | Allowance checks (`POST /v1/usage/check`).               |
| Usage write key | `zc_` + 48 chars, scope `usage:write`              | Usage reports (`POST /v1/usage`).                        |

A single API key carrying both usage scopes works in place of the two scoped keys. Every SDK accepts either form and falls back to the combined key when a scoped key is unset.

ZeroClick shows every secret once, at creation, and cannot show it again. The dashboard displays the first characters of each API key for recognition. Put them in your secret manager immediately:

```sh theme={null}
ZEROCLICK_SIGNING_SECRET_KID=hsec_k5nq0v7m3d8p
ZEROCLICK_SIGNING_SECRET=zcsec_…
ZEROCLICK_API_KEY=zc_…
```

## The kid must match exactly

The dashboard shows the signing secret's key id, the `kid` (format `hsec_…`), next to the secret. The kid arrives in every `zc-signature` header (`t=…,kid=hsec_k5nq0v7m3d8p,v1=…`). The SDKs use the kid to select which secret verifies each request. The key in your secrets map must match the dashboard's key id character for character. A mismatched kid fails every request with `unknown_kid` and the `401` refusal.

## Scopes

Send API keys as `Authorization: Bearer zc_…` to `https://api.zeroclick.io`. Each key carries one or more of four scopes:

| Scope         | Grants                                                                                                                                                                     |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `usage:read`  | Allowance checks: `POST /v1/usage/check`.                                                                                                                                  |
| `usage:write` | Usage reports: `POST /v1/usage`.                                                                                                                                           |
| `admin:read`  | Reading your catalog: sellers, services, meters, plans.                                                                                                                    |
| `admin:write` | Managing the catalog over the [REST API](/api-reference/introduction), including minting signing secrets. That is enough for an agent to automate seller setup end to end. |

Manage keys in the dashboard under **Settings → API keys**, in a signed-in session only: API keys cannot create or revoke API keys. Requests with a missing scope get `403 {"error":"insufficient_scope"}`. See [errors](/resources/errors).

## Split read and write keys

The process that guards requests only needs to read allowances; the process that reports usage only needs to write. Splitting the keys follows least privilege and matches a common deployment shape. The read key lives in the service that serves requests; the write key lives in the worker that reports usage asynchronously.

<CodeGroup>
  ```ts TypeScript theme={null}
  const zeroClick = createSeller({
    signingSecrets: {
      [process.env.ZEROCLICK_SIGNING_SECRET_KID!]:
        process.env.ZEROCLICK_SIGNING_SECRET!,
    },
    usageReadKey: process.env.ZEROCLICK_USAGE_READ_KEY!,
    usageWriteKey: process.env.ZEROCLICK_USAGE_WRITE_KEY!,
  });
  ```

  ```python Python theme={null}
  zeroclick = create_async_seller(
      signing_secrets={
          os.environ["ZEROCLICK_SIGNING_SECRET_KID"]: os.environ[
              "ZEROCLICK_SIGNING_SECRET"
          ]
      },
      usage_read_key=os.environ["ZEROCLICK_USAGE_READ_KEY"],
      usage_write_key=os.environ["ZEROCLICK_USAGE_WRITE_KEY"],
  )
  ```

  ```go Go theme={null}
  seller, err := sellers.New(sellers.Config{
  	UsageReadKey:   os.Getenv("ZEROCLICK_USAGE_READ_KEY"),
  	UsageWriteKey:  os.Getenv("ZEROCLICK_USAGE_WRITE_KEY"),
  	ServiceSlug:    "product-watch",
  	SigningSecrets: secrets,
  })
  ```
</CodeGroup>

The read key covers `guard` and the direct allowance check; the write key covers usage reporting. When you pass both scoped keys, omit the combined `apiKey`.

## Rotate a signing secret

ZeroClick signs each forwarded request with your newest active secret and names it by kid, so rotation is a three-step overlap, not a cutover:

<Steps>
  <Step title="Create the new secret in the dashboard">
    ZeroClick starts signing new requests with it. The signature's `kid` changes to the new secret's id.
  </Step>

  <Step title="Hold both kids in your SDK configuration">
    Requests signed with either secret verify while both are configured:

    <CodeGroup>
      ```ts TypeScript theme={null}
      const zeroClick = createSeller({
        signingSecrets: {
          hsec_k5nq0v7m3d8p: process.env.ZEROCLICK_SIGNING_SECRET_PREVIOUS!,
          hsec_2b9fj6wt5xr0: process.env.ZEROCLICK_SIGNING_SECRET_CURRENT!,
        },
        apiKey: process.env.ZEROCLICK_API_KEY!,
      });
      ```

      ```python Python theme={null}
      zeroclick = create_async_seller(
          signing_secrets={
              "hsec_k5nq0v7m3d8p": os.environ["ZEROCLICK_SIGNING_SECRET_PREVIOUS"],
              "hsec_2b9fj6wt5xr0": os.environ["ZEROCLICK_SIGNING_SECRET_CURRENT"],
          },
          api_key=os.environ["ZEROCLICK_API_KEY"],
      )
      ```

      ```go Go theme={null}
      // ZEROCLICK_SIGNING_SECRETS="hsec_k5nq0v7m3d8p:<previous>,hsec_2b9fj6wt5xr0:<current>"
      secrets, err := sellers.SecretsFromEnv()
      if err != nil {
      	log.Fatal(err)
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Revoke the old secret, then drop it">
    Once requests signed with the old secret can no longer be in flight, revoke it in the dashboard. Verification rejects signatures older than the 300-second tolerance anyway, so a few minutes of overlap is enough. Then remove the old kid from your configuration.
  </Step>
</Steps>

Never log or return a signing secret, and never send it anywhere. It exists only to verify signatures inside your backend.

## Resolve secrets from a vault

If your secrets live in a secret manager rather than the environment, resolve them by kid instead of passing a static map:

<CodeGroup>
  ```ts TypeScript theme={null}
  const zeroClick = createSeller({
    resolveSigningSecret: async ({ kid }) => secretStore.get(kid),
    apiKey: process.env.ZEROCLICK_API_KEY!,
  });
  ```

  ```python Python theme={null}
  zeroclick = create_async_seller(
      resolve_signing_secret=lambda kid: secret_store.get(kid),
      api_key=os.environ["ZEROCLICK_API_KEY"],
  )
  ```

  ```go Go theme={null}
  seller, err := sellers.New(sellers.Config{
  	APIKey:      os.Getenv("ZEROCLICK_API_KEY"),
  	ServiceSlug: "product-watch",
  	Resolve: func(kid string) (string, bool, error) {
  		return secretStore.Get(kid)
  	},
  })
  ```
</CodeGroup>

The SDK calls the resolver with each request's kid. Signal "unknown kid" by returning `null` (TypeScript), `None` (Python), or `ok=false` (Go). That request gets the `401` refusal. An unreachable vault is a different case: raise or return an error so it surfaces loudly instead of refusing valid traffic. Rotation works the same way as the static map: keep the vault serving both kids until the old one ages out.

With keys in place, the first obligation is [verifying requests](/integrate/verify-requests).
