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

# Security

> Storing a key, the access model, and webhook URL requirements

## The key

A Developer API key reaches the whole tenant's data within its scopes and its owner's
permissions. Treat it as a service password.

* Keep it in a secret manager or an environment variable, not in the repository and
  not in a config file next to the code.
* Never use it in a frontend or mobile app, where every user can read it.
* Issue a separate key per integration. A compromise then costs you one key rather
  than all access.
* Rotation is manual: create a new key, switch the integration over, then revoke the
  old one. Keys currently have no expiry.
* The secret is shown once at creation. Lists show only the 12-character prefix.

<Warning>
  Never log the key value or paste it into examples that end up in an issue tracker or
  a chat. If a key may have leaked, revoke it in settings; revocation takes effect
  immediately.
</Warning>

## What a key can see

Effective access is the intersection of two limits:

1. **The key's scopes.** An endpoint requires a specific scope; without it the
   request is rejected with `DEVELOPER.SCOPE_INSUFFICIENT`.
2. **The owner's permissions.** The key sees exactly the employees, documents, and
   fields available to the permission profile of the user who created it. Permissions
   are evaluated per request: narrow the owner's access and the key narrows with it.

Two consequences follow. Sensitive fields the owner may not see are omitted from the
response, and their names are listed in `redacted_fields`. A single resource you
cannot access is indistinguishable from one that does not exist: both return `404`.

A key stops working once its owner is removed from the tenant or deactivated, so a
departure does not require a separate revocation — though reviewing the key list
after one is still worthwhile.

## Webhook URLs

Doodocs calls your server, so a subscription URL must satisfy two requirements:

* the `https` scheme only;
* a publicly resolvable address only: `localhost`, private ranges, and cloud
  metadata addresses are rejected with `DEVELOPER.WEBHOOK_URL_INVALID`.

For local debugging, expose a tunnel with a public `https` address.

## Verifying deliveries

Every delivery is signed: the `Doodocs-Signature` header carries a timestamp and an
HMAC-SHA256 of `{t}.{body}` computed with the subscription secret. Verify the
signature before parsing the body and reject requests older than five minutes, or an
intercepted delivery can be replayed.

The secret is shown once when the subscription is created and is not rotated. To
replace it, create a new subscription with the same URL and event types, switch your
verification to the new secret, and delete the old one.

Event payloads are deliberately thin: type, time, and a resource id. Personal data
never leaves for an external URL — you fetch it with your own key, where scopes and
field redaction apply.

<Note>
  Document events are delivered for every document in the tenant the endpoint
  subscribes to, regardless of the key owner's per-document permissions. The event
  itself carries only an id and a timestamp; the document's content stays gated by
  permissions when you read it through `GET /documents/{id}`.
</Note>
