Перейти к содержанию

Начало работы

Это краткое руководство поможет вам шаг за шагом: создать API-ключ и выполнить первые запросы к Pert API.

В итоге вы сможете:

  • создать и подтвердить API-пользователя
  • создать vault-аккаунт
  • отправить свою первую транзакцию

Как работает аутентификация API

Pert использует асимметричную криптографию для защиты взаимодействия с API. Проще говоря, это работает так:

Компонент Описание
Секретный API-ключ
(приватный ключ)
Хранится только у вас и используется для подписи авторизационного запроса, чтобы подтвердить, что он отправлен именно вами
Публичный ключ Находится у Pert и нужен для проверки, что запрос действительно подписан вашим приватным ключом

Такая архитектура гарантирует, что ваш секретный API-ключ никогда не покидает ваше окружение — в Pert передаётся только публичный ключ. Мы не можем подписывать запросы за вас, и даже в случае компрометации системы Pert ваши учётные данные останутся в безопасности.

Шаг 1: Генерация пары ключей

В каждом запросе к API Pert должен передаваться авторизационный JWT-токен:

Заголовок Значение
Authorization Bearer JWT_TOKEN

Для получения авторизационного JWT-токена необходимо отправить запрос подписанный ассиметричным ключом шифрования.

Используйте OpenSSL для генерации пары ключей ed25519

# генерируем приватный ключ
openssl genpkey -algorithm ED25519 -out "pert_secret.pem"

# генерируем публичный ключ
openssl pkey -in "pert_secret.pem" -pubout -out "pert_public.pem"

В результате выполнения этой команды создаются:

  • pert_secret.pem — ваш приватный Ed25519-ключ, который используется как API-секрет
  • pert_public.pem — файл публичного ключа, который нужно будет загрузить при создании API-ключа

Обязательно храните файл с секретным ключом в надёжном и безопасном месте.

Шаг 2: Создание API-пользователя

Чтобы создать API-пользователя с правами подписи, выполните следующие шаги:

  • В консоли Pert откройте раздел Пользователи → По API
  • Нажмите Создать пользователя
  • Укажите название и описание(не обязательно) пользователя, выберите нужную роль (например, SIGNER, если он должен подписывать транзакции) и загрузите файл публичного ключа, созданный на шаге 1
  • Нажмите Сохранить

После одобрения заявки новый API-пользователь появится в списке. Вы получите API Key (ID пользователя API) — уникальный идентификатор, который используется вместе с вашим секретным ключом.

Шаг 3: Получение авторизационного токена

Чтобы получить JWT-токен, отправьте подписанный JSON-payload на эндпоинт POST /api/v1/auth/access-token.

Структура подписываемого payload:

Поле Тип Описание
client_type string user для API-пользователя
client_id string UUID API-пользователя (выдан на шаге 2)
timestamp int64 Unix-время в секундах. Должно быть в окне ±5 минут от серверного времени
nonce string Случайная строка для защиты от replay-атак (16+ байт энтропии, base64 или hex)

Тело запроса:

Поле Тип Описание
data string base64-кодированный JSON payload
signature string base64-кодированная Ed25519-подпись сырых байт JSON (до base64-кодирования)

Подпись считается над сырыми байтами JSON

Подписывается JSON до base64-кодирования. Не подписывайте base64-строку и не пересериализуйте JSON между подписью и отправкой — это сломает верификацию на стороне сервера.

Используйте серверное время

Если на вашем хосте сильный clock drift, запросы будут отвергаться с ошибкой request timestamp too old / too far in future. Получите серверное время через GET /api/v1/auth/time и используйте его при формировании timestamp.

Пример

import base64
import json
import secrets
import time
import requests
from cryptography.hazmat.primitives.serialization import load_pem_private_key

BASE_URL  = "https://auth.pert.paranoid.security"
API_USER_ID = "<API_USER_UUID>"
SECRET_PEM  = "pert_secret.pem"

with open(SECRET_PEM, "rb") as f:
    private_key = load_pem_private_key(f.read(), password=None)

payload = {
    "client_type": "user",
    "client_id":   API_USER_ID,
    "timestamp":   int(time.time()),
    "nonce":       secrets.token_urlsafe(16),
}
data_bytes = json.dumps(payload, separators=(",", ":")).encode()
signature  = private_key.sign(data_bytes)

resp = requests.post(
    f"{BASE_URL}/api/v1/auth/access-token",
    json={
        "data":      base64.b64encode(data_bytes).decode(),
        "signature": base64.b64encode(signature).decode(),
    },
    timeout=10,
)
resp.raise_for_status()
access_token = resp.json()["access_token"]
package main

import (
    "bytes"
    "crypto/ed25519"
    "crypto/rand"
    "crypto/x509"
    "encoding/base64"
    "encoding/json"
    "encoding/pem"
    "net/http"
    "os"
    "time"
)

type signed struct {
    ClientType string `json:"client_type"`
    ClientID   string `json:"client_id"`
    Timestamp  int64  `json:"timestamp"`
    Nonce      string `json:"nonce"`
}

func main() {
    pemBytes, _ := os.ReadFile("pert_secret.pem")
    block, _ := pem.Decode(pemBytes)
    keyAny, _ := x509.ParsePKCS8PrivateKey(block.Bytes)
    priv := keyAny.(ed25519.PrivateKey)

    nonce := make([]byte, 16)
    _, _ = rand.Read(nonce)

    data, _ := json.Marshal(signed{
        ClientType: "user",
        ClientID:   "<API_USER_UUID>",
        Timestamp:  time.Now().Unix(),
        Nonce:      base64.RawURLEncoding.EncodeToString(nonce),
    })
    sig := ed25519.Sign(priv, data)

    body, _ := json.Marshal(map[string]string{
        "data":      base64.StdEncoding.EncodeToString(data),
        "signature": base64.StdEncoding.EncodeToString(sig),
    })
    http.Post("https://auth.pert.paranoid.security/api/v1/auth/access-token",
        "application/json", bytes.NewReader(body))
}
# подписываемый payload (timestamp — секунды)
DATA_JSON=$(printf '{"client_type":"user","client_id":"%s","timestamp":%d,"nonce":"%s"}' \
    "$API_USER_ID" "$(date +%s)" "$(openssl rand -base64 16 | tr -d '=\n')")

# подпись над сырым JSON
DATA_B64=$(printf '%s' "$DATA_JSON" | base64 | tr -d '\n')
SIG_B64=$(printf '%s' "$DATA_JSON" \
    | openssl pkeyutl -sign -inkey pert_secret.pem -rawin \
    | base64 | tr -d '\n')

curl -X POST https://auth.pert.paranoid.security/api/v1/auth/access-token \
    -H "Content-Type: application/json" \
    -d "{\"data\":\"$DATA_B64\",\"signature\":\"$SIG_B64\"}"

Ответ:

{
  "access_token": "eyJhbGciOi...",
  "expires_at":   "1777300943056"
}
Поле Описание
access_token Bearer-токен, передавайте в заголовке Authorization: Bearer <token>
expires_at Время истечения токена в миллисекундах Unix (строкой)

Кешируйте токен и обновляйте заранее

Не запрашивайте новый токен на каждый вызов API. Сохраните access_token и обновите его за ~30 секунд до expires_at, повторив процедуру подписи.

Шаг 4: Создание vault-аккаунта

Все остальные запросы помимо Authorization требуют заголовок X-Workspace-IdUUID вашего workspace. Узнать его можно в консоли (URL …/workspaces/<UUID>/…) или через GET /api/v1/workspace.

Создайте vault — логический контейнер для адресов в разных валютах:

curl -X POST https://wallet.pert.paranoid.security/api/v1/vault \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "X-Workspace-Id: $WORKSPACE_ID" \
    -H "Content-Type: application/json" \
    -d '{"name": "My first vault"}'

Ответ содержит id созданного vault — он понадобится дальше.

Добавьте wallet нужной валюты — для каждой валюты в vault создаётся отдельный wallet с депозитным адресом. Список доступных валют — GET /api/v1/currencies:

curl -X POST https://wallet.pert.paranoid.security/api/v1/wallet \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "X-Workspace-Id: $WORKSPACE_ID" \
    -H "Content-Type: application/json" \
    -d '{"vault_id": "'"$VAULT_ID"'", "currency_id": "'"$CURRENCY_ID"'"}'

В ответе вернётся массив assets — каждый элемент содержит id wallet и его депозитный address. id wallet — это source_wallet_id для исходящих транзакций.

Шаг 5: Отправка первой транзакции

Перед отправкой можно оценить комиссию через POST /api/v1/transactions/fee — это не обязательно, но рекомендуется.

Создание транзакции:

curl -X POST https://wallet.pert.paranoid.security/api/v1/transaction \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "X-Workspace-Id: $WORKSPACE_ID" \
    -H "X-Idempotency-Key: $(uuidgen)" \
    -H "Content-Type: application/json" \
    -d '{
          "source_wallet_id": "'"$SOURCE_WALLET_ID"'",
          "target_address":  "'"$TARGET_ADDRESS"'",
          "amount":          "0.01"
        }'
Заголовок Описание
X-Idempotency-Key UUID запроса. Повторный запрос с тем же ключом не создаст вторую транзакцию (вернёт 409)

Ответ 201 Created содержит созданную транзакцию с её id и начальным статусом. Транзакция не отправляется в сеть мгновенно — она проходит через скрининг, аппрувалы по политикам и MPC-подписание. Финальный статус (broadcasted, confirmed, rejected и т.д.) приходит позже.

Подробнее про статусы и pipeline — в разделе Pert API.

Что дальше

  • Авторизация — детали JWT-токенов, обновление, introspect.
  • Webhooks — подпишитесь на события, чтобы получать обновления по транзакциям и депозитам без поллинга.
  • Pert API — полный справочник эндпоинтов.
  • Коды ошибок — что означают коды ответа и как их обрабатывать.