Авторизация
Pert API использует JWT-токены в заголовке Authorization. Сам токен выдаётся
в обмен на запрос, подписанный вашим Ed25519-секретным ключом — без передачи
секрета по сети.
Получение токена «end-to-end» с примерами кода описано в разделе Начало работы. Этот раздел — справочник: что входит в подписываемый payload, как валидируется запрос, сколько живёт токен, как его обновлять и проверять.
Заголовки запросов
| Заголовок | Значение | Когда нужен |
|---|---|---|
Authorization |
Bearer <ACCESS_TOKEN> |
Все эндпоинты, кроме /api/v1/auth/access-token и /api/v1/auth/time |
X-Workspace-Id |
UUID workspace | Все эндпоинты, работающие в контексте workspace (vault, wallet, transaction, …) |
Workspace выбирается заголовком
Токен может содержать доступы сразу к нескольким workspace. Конкретный
контекст для каждого запроса определяется заголовком X-Workspace-Id —
если в токене нет доступов к этому workspace, запрос завершится ошибкой 403.
Получение access token
POST /api/v1/auth/access-token
Подписываемый payload
JSON-объект со следующими полями:
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
client_type |
string | да | user — API-пользователь |
client_id |
string | да | UUID API-пользователя |
timestamp |
int64 | да | Unix-время в секундах (см. Серверное время) |
nonce |
string | да | Уникальная случайная строка (≥16 байт энтропии). Гарантирует уникальность подписи |
Сервер валидирует payload:
timestampдолжен быть не старше 5 минут и не более чем на 1 минуту в будущем относительно серверного времени;nonceобязателен и не должен быть пустым;client_idдолжен соответствовать активному API-пользователю в указанной роли.
Тело запроса
| Поле | Тип | Описание |
|---|---|---|
data |
string | base64-кодированный JSON payload |
signature |
string | base64-кодированная Ed25519-подпись сырых байт JSON (до base64-кодирования) |
Что именно подписывается
Подпись считается над байтами JSON до base64-кодирования. Не подписывайте base64-строку и не меняйте сериализацию JSON (порядок полей, пробелы) после подписи — проверка на сервере сломается.
Ответ
{
"access_token": "eyJhbGciOi...",
"expires_at": "1777300943056"
}
| Поле | Тип | Описание |
|---|---|---|
access_token |
string | JWT, передавайте в заголовке Authorization: Bearer … |
expires_at |
string | Время истечения токена в миллисекундах Unix (строкой) |
Серверное время
GET /api/v1/auth/time
Эндпоинт открыт без авторизации и возвращает текущее серверное время:
{
"unix_seconds": "1777300943",
"unix_milliseconds": "1777300943056"
}
Когда использовать
Если ваш хост может расходиться по времени с сервером больше, чем на минуту,
запрашивайте серверное время перед каждой генерацией подписи и берите
unix_seconds как timestamp payload. Это исключает отказы из-за clock drift.
TTL и обновление токена
- Срок жизни access token, выданного по публичному ключу — 1 час.
- Refresh-токен не выдаётся: для обновления повторите процедуру подписи и получите новый access token.
- Поле
expires_at(миллисекунды Unix) — единственный источник истины по времени истечения.
Рекомендуемая стратегия обновления:
- Сохраните
access_tokenиexpires_atв памяти приложения. - Перед каждым запросом сверяйте: если до
expires_atосталось ≤ 30 секунд — обновите токен. - На ответ
401 Unauthorizedот любого эндпоинта — обновите токен и повторите запрос один раз.
Не запрашивайте новый токен на каждый вызов
Каждый вызов /api/v1/auth/access-token — это сетевой round-trip и подпись
на вашей стороне. На высоконагруженных интеграциях это превращается в узкое
место. Кешируйте токен и переиспользуйте до истечения.
Что внутри токена
Access token — это подписанный EdDSA (Ed25519) JWT. Полезная нагрузка содержит
идентификатор субъекта и массив доступов accesses — по одному элементу на каждый
workspace, к которому у API-пользователя есть роль:
| Поле в access | Описание |
|---|---|
workspace_uuid |
UUID workspace |
role |
Роль API-пользователя в этом workspace (ROLE_SIGNER, ROLE_VIEWER, ROLE_APPROVER, …) |
permissions |
Набор разрешений роли |
role_status |
Статус роли (ROLE_STATUS_ACTIVE для рабочей роли) |
Множественные workspace
Если API-пользователю выдана роль в нескольких workspace одного аккаунта, один
токен покрывает их все. Контекст конкретного запроса определяется заголовком
X-Workspace-Id.
Проверка токена: introspect
POST /api/v1/auth/introspect
Возвращает детали по текущему access token: статус активности, время выдачи и истечения, субъект и список доступов. Полезно для отладки интеграции и для администраторских сценариев — на горячем пути авторизации использовать не нужно (вся информация уже есть в самом JWT).
curl -X POST https://auth.pert.paranoid.security/api/v1/auth/introspect \
-H "Authorization: Bearer $ACCESS_TOKEN"
Ответ:
{
"active": true,
"subject_type": "user",
"subject_uuid": "550e8400-e29b-41d4-a716-446655440000",
"issued_at": "1777299043056",
"expires_at": "1777300943056",
"jti": "f1b2…",
"accesses": [
{
"workspace_uuid": "a1b2…",
"role": "ROLE_SIGNER",
"permissions": ["TRANSACTION_CREATE", "TRANSACTION_APPROVE", "..."],
"role_status": "ROLE_STATUS_ACTIVE"
}
]
}
Ошибки
| HTTP | Причина | Что делать |
|---|---|---|
| 400 | Невалидный формат payload, timestamp вне допустимого окна, пустой nonce |
Сверьтесь с серверным временем, проверьте JSON-сериализацию |
| 401 | Невалидная подпись, истёкший токен, отсутствует Authorization |
Перегенерируйте подпись/токен. Убедитесь, что подписываете сырой JSON, а не base64 |
| 403 | Нет доступа к workspace в X-Workspace-Id или у роли нет нужного permission |
Проверьте accesses через introspect; запросите нужные права у владельца workspace |
| 404 | API-пользователь не найден или деактивирован | Проверьте client_id и статус пользователя в консоли |
Безопасность приватного ключа
- Хранилище: secrets-менеджер (Vault, AWS/GCP/Azure Secrets Manager, KMS), не репозиторий и не env-файл в образе.
- Доступ: только тот процесс, который реально подписывает запросы. Никаких
chmod 644. - Ротация: создайте нового API-пользователя с новой парой ключей, переключите интеграцию, затем деактивируйте старого. Не используйте один
client_idс двумя ключами. - Компрометация: немедленно деактивируйте API-пользователя в консоли. Все ранее выданные токены продолжают работать до
expires_at(максимум 1 час), новые получить уже не получится.
Один API-пользователь — одна интеграция
Не используйте один client_id/секрет в нескольких разных сервисах. При
компрометации одного отзыв затронет всех — а аудит-лог не различит источник
запросов.