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

# OAuth client credentials

> Provision a scoped service-account client, handle one-time secrets, and recover safely from uncertain requests.

Use OAuth client credentials when your backend integration needs short-lived
access tokens or your security policy requires them. Each client belongs to
one service account and one organization. **OAuth is optional:** a scoped
API key is sufficient for a private server integration that can securely store
and rotate a key. See [Authentication](/developers/v1/authentication) to choose
between the two methods and understand how service accounts fit.

<Note>
  OAuth access must be enabled for your organization. Your setup provides the
  regional token endpoint, issuer, audience and client authentication method.
  If you need these settings or cannot access client management, contact
  Living Security Support.
</Note>

The client credentials grant represents your service account. It does not sign in a person
or ask a customer to authorize an application. Delegated authorization through a
browser is a separate OAuth capability and is not offered by these operations.

## Before you start

* Create a [service account](/developers/developer-hub/service-accounts) with only
  the permissions the integration needs.
* Use a management credential with `service_account:manage` and each permission
  you want to grant to the new client. The target account must also hold those
  permissions. A client cannot expand either account's authority.
* Choose the organization's [region](/developers/v1/regions). API credentials and
  OAuth access tokens belong on the matching regional API host.
* Keep the client secret and tokens in your server-side secret storage. Never put
  them in browser code, mobile applications, source control, or request logs.

## Create a client

`POST /v1/oauth_clients` requires `service_account_id`, a nonempty list of explicit
`scopes`, and an `Idempotency-Key`. Wildcards are not accepted. A client supports
up to 100 scopes, with a combined length of at most 4,096 characters. Each service
account can have at most 20 clients or unresolved creation/cleanup operations.
Revocation immediately denies API access. A client or an uncertain provider
creation continues to count against the limit until remote cleanup is confirmed.
An abandoned reservation or a create proven to have failed before provider work
began can release its slot safely. Its retry key still belongs to the original
attempt; recover that receipt before deciding whether to create a replacement.

Choose one retry key for this creation attempt and persist it **before** sending
the request. Use 1–255 visible ASCII characters, without spaces. Reuse it with the
same account, scopes, management principal, region, and API version after a lost
response. Rotating the management credential under the same service account does
not create a new retry identity.

Install Python's `requests` package. Load the following environment values through
your normal secret and configuration management:

| Variable | Value |
| - | - |
| `LIVING_SECURITY_API_KEY` | Management API key |
| `LIVING_SECURITY_REGION` | `us` or `eu` |
| `LIVING_SECURITY_SERVICE_ACCOUNT_ID` | Target service account ID |
| `LIVING_SECURITY_OAUTH_CREATE_KEY` | Previously persisted retry key |

```python Python theme={null}
import os
import requests

HOSTS = {
    "us": "https://api.us-east-1.livingsecurity.ai/v1",
    "eu": "https://api.eu-west-1.livingsecurity.ai/v1",
}
base = HOSTS[os.environ["LIVING_SECURITY_REGION"]]
response = requests.post(
    f"{base}/oauth_clients",
    auth=("apikey", os.environ["LIVING_SECURITY_API_KEY"]),
    headers={
        "Living-Security-Version": "2026-07-01",
        "Idempotency-Key": os.environ["LIVING_SECURITY_OAUTH_CREATE_KEY"],
    },
    json={
        "service_account_id": os.environ["LIVING_SECURITY_SERVICE_ACCOUNT_ID"],
        "scopes": ["organization:read"],
    },
    timeout=(5, 30),
    allow_redirects=False,
)
try:
    if response.status_code != 201:
        raise RuntimeError(
            f"Client creation needs review: HTTP {response.status_code}; "
            f"request_id={response.headers.get('Request-Id', 'unknown')}"
        )
    client = response.json()
    if client.get("object") != "oauth_client" or not client.get("id"):
        raise RuntimeError("Unexpected client receipt; preserve the retry key")
    if client.get("status") != "active" or not client.get("client_secret"):
        raise RuntimeError(
            f"Client {client['id']} needs recovery; no usable secret was returned"
        )
    # Persist client['id'], client['client_id'], and client['client_secret']
    # directly in your secret manager before using the client. Do not print them.
finally:
    response.close()
```

The example makes one request and keeps the result in memory. Add your secret
manager's write operation at the marked point before running it in production.
It deliberately requires a decision after an uncertain result instead of silently
creating another client. Network exceptions also require the recovery steps below.

Creation and receipt responses use `201` and contain an `oauth_client` resource
with `id`, `service_account_id`, `client_id`, `scopes`, `status`, `created_at`, and
`client_secret`. Responses are marked `Cache-Control: no-store`.

* **`id`** identifies the platform resource. Save it for retrieval and revocation.
* **`client_id`** identifies the client during token exchange. It is not the
  platform resource ID. It can be `null` while creation is pending.
* **`client_secret`** is returned only to the request that completes creation.
  Every repeated creation request returns `null`, including a retry after a lost
  success response. Retrieval never returns a secret.

## Recover a lost or interrupted response

Repeat the same creation request with the saved retry key to recover a resource
receipt. The receipt reflects current state, so a revoked client remains revoked.
This operation does not use the general 24-hour cached-response replay contract
and does not return an `Idempotent-Replayed` header.

| Result | Next action |
| - | - |
| `active`, secret present | Save the secret securely and continue onboarding. |
| `active`, secret absent | The secret cannot be recovered. Revoke this client, confirm revocation, then create a replacement using a new retry key. |
| `pending` | Creation is unconfirmed. Keep the same retry key. Retrieve the resource to inspect state; do not create duplicates to bypass uncertainty. |
| `revoked`, `disabled`, or `expired` | Do not use this client. Confirm the intended account configuration before provisioning a replacement. |
| `409 idempotency_key_reuse` | The request changed under an existing key. Recover the original operation before deciding whether a separate creation is needed. |
| `409 invalid_request` | Check the target account's status and grants. If its client limit was reached, complete cleanup before retrying; changing the retry key does not free capacity. |
| `429` | Honor `Retry-After`; retain the same key and request. |
| `500`, `503`, or a network timeout | The outcome may be unknown. Preserve the key and inputs; recover the receipt before creating another client. |
| `401` or `403` | Resolve credential, permission, or entitlement access before retrying. |
| `404` | Confirm OAuth enablement, regional host, and resource ownership. |

See [errors](/developers/v1/errors/overview) for request IDs and recovery details.
Never send a client secret or access token with a support request.

## Inspect and revoke

Use your management credential and the platform resource `id`:

| Operation | Request | Success |
| - | - | - |
| Inspect current state | `GET /v1/oauth_clients/{id}` | `200`, client resource without a secret |
| Revoke access | `DELETE /v1/oauth_clients/{id}` | `200`, revoked client resource without a secret |

Revocation blocks subsequent API requests for the client, including requests with
already-issued tokens. It does not cancel work already accepted by the API.
Remote credential cleanup can complete later. Repeating revocation is safe and
requires no retry key. Continue using the management credential if the client
being revoked is itself an API caller.

## Rotate without losing access

Create a second client with the required scope ceiling. Save its secret, obtain a
token using your regional token endpoint, and verify the integration works
with it. Switch the integration to the replacement, then revoke the old resource.
Retain an independent management credential so you can recover from a bad rollout.

Access tokens cannot grant more than the client's scopes and the service account's
current permissions. Removing account permissions or revoking a client takes
precedence over a token's remaining lifetime. Do not treat a cached token as proof
that authorization is still valid. Obtain a new access token before expiry by
authenticating the client again; this grant does not use refresh tokens.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.