# Authentication

> Machine-to-machine tokens, workflow selection, and authentication failures

Every `/data` and `/func` call needs an access token and workflow identifier.
The token identifies the organization. The workflow selects a configured
integration in that organization. A request with only one is rejected.

## Get an access token

The access token is an OAuth 2.0 machine-to-machine JWT issued by Stytch. Use
the client credentials grant with the client ID and client secret Datastruct
issues for the workflow. Data Serve verifies tokens against a cached Stytch
JSON Web Key Set (JWKS). The cache lifetime is one hour.

> **Confirmation needed:** Confirm the public token endpoint, the exact client
> credentials request, and the token lifetime. Datastruct's internal token
> command is not a public interface.

Cache a token for its lifetime. Do not request one for every API call. The
organization comes from token claims and cannot be changed in the body.

## Send credentials

### Request body

```json
{
  "access_token": "<access-token>",
  "workflow": "wfl_01jykdqt8yejf9hz2yvcqwb4gd",
  "config": {},
  "inputs": {}
}
```

### Datastruct headers

```http
X-Datastruct-Access-Token: <access-token>
X-Datastruct-Workflow: wfl_01jykdqt8yejf9hz2yvcqwb4gd
```

### Bearer header

```http
Authorization: Bearer <access-token>
X-Datastruct-Workflow: wfl_01jykdqt8yejf9hz2yvcqwb4gd
```

Use Datastruct headers or the bearer header for new integrations so the token
stays out of body-level logging.

## Resolution order

Data Serve reads the token from the first source that supplies one:

1. `access_token` in the body.
2. `X-Datastruct-Access-Token`.
3. `Authorization: Bearer`.

> The token and workflow are read from the same source. If the body contains
> `access_token`, Data Serve reads only the body field `workflow`. It ignores
> `X-Datastruct-Workflow` in that request.

## Workflow validation

The workflow must exist and belong to the token organization. A workflow from
another organization fails in the same way as an invalid token.

## Authentication failures

### No credential

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```

```json
{ "detail": "Access token required" }
```

### Rejected credential

```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```

```json
{ "detail": "Authentication failed" }
```

The rejected response covers malformed, expired, or invalid tokens; unknown
organizations; missing workflows; and workflows outside the organization. It
does not identify which condition applied.

## Credential handling

- Store client secrets in a secret manager.
- Never commit client secrets or access tokens.
- Redact tokens from logs and error reports.
- Separate test and production credentials.
- Report exposed credentials to Datastruct for revocation.
- Never place a token in browser code. Data Serve sends no CORS headers.

## Vendor credentials

The access token authenticates the caller to Datastruct. Credentials used to
call vendors are separate. See
[Source configuration](/docs/api/data-serve/data-sources/).
