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

# endstate.taps.create

> Records a tap and returns the response verbatim. Prefer verify(), which hands back a session bound to the tap.

```ts theme={null}
create(body: CreateTapRequest, options?: IdempotentRequestOptions): Promise<TapResponse>
```

|                 |                                                                               |
| --------------- | ----------------------------------------------------------------------------- |
| Endpoint        | [`POST /v1/taps`](/api-reference/taps/record-a-tap-and-issue-a-session-token) |
| Credential      | Secret key (`end_sk_...`), Publishable key (`end_pk_...`)                     |
| Retry class     | `keyed`                                                                       |
| Idempotency key | Accepted                                                                      |

Carries an idempotency key. Retried on network failure, timeout, `429`, and `409 idempotency.in_progress` - never on a `5xx`.

## Example

```ts theme={null}
const result = await endstate.taps.create({
  chip_id: "ABCDEF0123",
  e: "C78566198547116F3A715DC1C62AF96F",
});
```

## Parameters

<ParamField body="chip_id" type="string" required>
  Chip identifier from the tap URL.
</ParamField>

<ParamField body="e" type="string" required>
  The single-use value from the tap URL. Each one works exactly once.
</ParamField>

<ParamField body="c" type="string">
  Tap verification code (`c`) from the tap URL. Include it whenever the tag provides one.
</ParamField>

<ParamField body="ttl" type="integer | null">
  Session-token TTL override in seconds, between 60 and 3600 inclusive. Secret key only: a publishable-key caller does not choose how long its own credential lives.
</ParamField>

<ParamField body="dry_run" type="boolean">
  Validate the tap without recording it or issuing a session token. Requires an admin secret key.
</ParamField>

## Returns

`201` - `TapResponse`.

<ResponseField name="id" type="string | null" required>
  Tap identifier. null when dry\_run is true, because no tap is recorded.
</ResponseField>

<ResponseField name="session_token" type="object | null" required>
  Opaque session token scoped to this organization, chip, unit, and tap. null on dry\_run.
</ResponseField>

<ResponseField name="chip" type="object" required />

<ResponseField name="unit" type="object" required />

<ResponseField name="redirect_url" type="string | null" required>
  Canonical verify URL on the org's verified domain. null when no verified domain exists.
</ResponseField>

<ResponseField name="dry_run" type="boolean" required />

## Errors

Branch on `error.code`, never on the HTTP status - several codes share one.

| Code                       | HTTP | Retry         |
| -------------------------- | ---- | ------------- |
| `auth.forbidden`           | 403  | Do not retry  |
| `auth.unauthorized`        | 401  | Do not retry  |
| `chip.already_scanned`     | 410  | Do not retry  |
| `chip.invalid_e_value`     | 422  | Do not retry  |
| `chip.not_found`           | 404  | Do not retry  |
| `idempotency.in_progress`  | 409  | Safe to retry |
| `idempotency.key_conflict` | 409  | Do not retry  |
| `internal.error`           | 500  | Safe to retry |
| `rate_limit.exceeded`      | 429  | Safe to retry |
| `unit.not_found`           | 404  | Do not retry  |
| `validation.failed`        | 400  | Do not retry  |

See [Errors and retries](/sdks/core/errors-and-retries) for the error types and how to narrow them.
