# Authentication and Tokens

Issue, refresh, and revoke tokens, and send an Access Token with business requests.

import ApiDebugger from '../../../../components/ApiDebugger.astro';

## Credential lifecycle

| Credential    | Prefix | Default lifetime | Purpose                                                                   |
| ------------- | ------ | ---------------- | ------------------------------------------------------------------------- |
| App Key       | `ak_`  | Long-lived       | Identifies the application and remains visible in the admin console       |
| App Secret    | `sk_`  | Until reset      | Proves the application identity and is shown only after creation or reset |
| Access Token  | `oat_` | 24 hours         | Authorizes protected API requests                                         |
| Refresh Token | `ort_` | 30 days          | Rotates both the Access Token and Refresh Token                           |

Resetting the App Secret or disabling the application immediately revokes all active tokens for that application.

## Issue tokens

| Item | Value |
| --- | --- |
| Method | `POST` |
| Request URL | `https://backend.biotech-mkt.com/openapi/auth/token` |
| Authentication | App Key + App Secret |
| Permission | No endpoint grant required |
| Content-Type | `application/json` |

<ApiDebugger preset="issue-token" lang="en" />

This endpoint does not require an Access Token. Send a JSON body:

### Request body

| Field        | Type   | Required | Constraint             |
| ------------ | ------ | -------- | ---------------------- |
| `app_key`    | string | Yes      | Maximum 64 characters  |
| `app_secret` | string | Yes      | Maximum 128 characters |

### Request example

```bash
curl --request POST "https://backend.biotech-mkt.com/openapi/auth/token" \
  --header "Content-Type: application/json" \
  --data '{"app_key":"ak_xxx","app_secret":"sk_xxx"}'
```

### Response example

```json
{
  "code": 200,
  "message": "Success",
  "data": {
    "token_type": "Bearer",
    "app_id": 1,
    "app_key": "ak_xxx",
    "access_token": "oat_xxx",
    "expires_in": 86400,
    "access_expires_at": "2026-08-27 12:00:00",
    "refresh_token": "ort_xxx",
    "refresh_expires_in": 2592000,
    "refresh_expires_at": "2026-09-25 12:00:00"
  }
}
```

### Response fields

| Field                | Return type | Description                                        |
| -------------------- | ----------- | -------------------------------------------------- |
| `token_type`         | string      | Always `Bearer`                                    |
| `app_id`             | integer     | Internal OpenAPI application ID                    |
| `app_key`            | string      | App Key of the current application                 |
| `access_token`       | string      | Full Access Token, returned only in this response  |
| `expires_in`         | integer     | Access Token lifetime in seconds; default 86400    |
| `access_expires_at`  | string      | Absolute Access Token expiration time              |
| `refresh_token`      | string      | Full Refresh Token, returned only in this response |
| `refresh_expires_in` | integer     | Refresh Token lifetime in seconds; default 2592000 |
| `refresh_expires_at` | string      | Absolute Refresh Token expiration time             |

On success, `data` contains the token type, application details, both tokens, their lifetimes in seconds, and absolute expiration timestamps. Authentication failures return `401` without revealing whether the App Key or App Secret was incorrect.

## Refresh tokens

| Item | Value |
| --- | --- |
| Method | `POST` |
| Request URL | `https://backend.biotech-mkt.com/openapi/auth/refresh` |
| Authentication | Refresh Token |
| Permission | No endpoint grant required |
| Content-Type | `application/json` |

<ApiDebugger preset="refresh-token" lang="en" />

### Request body

| Field           | Type   | Required | Constraint             |
| --------------- | ------ | -------- | ---------------------- |
| `refresh_token` | string | Yes      | Maximum 128 characters |

### Request example

```bash
curl --request POST "https://backend.biotech-mkt.com/openapi/auth/refresh" \
  --header "Content-Type: application/json" \
  --data '{"refresh_token":"ort_xxx"}'
```

### Response example

The response has the same fields as the issue-token endpoint, but both tokens and expiration timestamps are new:

```json
{
  "code": 200,
  "message": "Success",
  "data": {
    "token_type": "Bearer",
    "app_id": 1,
    "app_key": "ak_xxx",
    "access_token": "oat_new_xxx",
    "expires_in": 86400,
    "access_expires_at": "2026-08-27 13:00:00",
    "refresh_token": "ort_new_xxx",
    "refresh_expires_in": 2592000,
    "refresh_expires_at": "2026-09-25 13:00:00"
  }
}
```

Refresh uses token rotation. A successful response returns a new Access Token and a new Refresh Token, and both old tokens stop working.

:::caution[Atomic replacement]
Serialize refresh attempts for the same Refresh Token and replace both stored tokens in one transaction or atomic write. Do not let multiple processes refresh the same token concurrently.
:::

## Revoke tokens

| Item | Value |
| --- | --- |
| Method | `POST` |
| Request URL | `https://backend.biotech-mkt.com/openapi/auth/revoke` |
| Authentication | Refresh Token |
| Permission | No endpoint grant required |
| Content-Type | `application/json` |

<ApiDebugger preset="revoke-token" lang="en" />

### Request body

| Field           | Type   | Required | Constraint             |
| --------------- | ------ | -------- | ---------------------- |
| `refresh_token` | string | Yes      | Maximum 128 characters |

### Request example

```bash
curl --request POST "https://backend.biotech-mkt.com/openapi/auth/revoke" \
  --header "Content-Type: application/json" \
  --data '{"refresh_token":"ort_xxx"}'
```

### Response example

```json
{
  "code": 200,
  "message": "Success",
  "data": []
}
```

Revocation targets the token record identified by the Refresh Token. Repeating the request, or submitting a Refresh Token that no longer exists, is treated as an idempotent success.

## Send an Access Token

Use the standard Bearer header:

```http
Authorization: Bearer oat_xxx
```

Compatible alternative:

```http
X-Openapi-Token: oat_xxx
```

When both headers are present and the Bearer header is validly formatted, the server uses `Authorization`. Access Tokens are not accepted in URL query parameters.

## Authentication response headers

Token endpoints return:

- `Cache-Control: no-store`
- `Pragma: no-cache`
- `X-Request-Id: <request ID>`

A `401` response also includes `WWW-Authenticate: Bearer`.

## Errors and rate limits

Authentication endpoints can return `401`, `422`, `429`, `500`, or `503`. Deployment settings can override the defaults. See [Errors and Rate Limits](/en/guides/errors-rate-limits) for limits, `Retry-After`, and retry guidance.

## Security recommendations

- Store the App Secret and Refresh Token only in a server-side secret manager or encrypted storage.
- Log only token prefixes or irreversible fingerprints, never full values.
- Use HTTPS and never transmit credentials over plaintext HTTP.
- Reset the Secret immediately after a leak; doing so invalidates all existing tokens.
