Формат запросов
Все POST endpoints принимают Content-Type: application/json. Клиентские запросы отправляются с credentials: "omit", чтобы cookies не участвовали в API-логике.
Кэширование и Origin
Ответы возвращаются с Cache-Control: no-store. Browser requests с внешним Origin отклоняются сервером.
Секреты
API не принимает Steam-логин, пароль или полный maFile. В KV уходят только зашифрованный payload, lookup token и обёрнутые ключи.
GET /api/health
Лёгкая проверка готовности для мониторинга, smoke-тестов после deploy и статуса в интерфейсе. Endpoint не читает и не изменяет пользовательские хранилища.
Запрос
Query-параметры и тело запроса не нужны. Отправляйте Accept: application/json и не кэшируйте результат.
Ответ
200 возвращает { "ok": true, "service": "sda-cloudflare-pages", "version": 1 }. 503 означает, что binding SDA_KV не настроен или ещё недоступен.
fetch("/api/health", {
headers: { accept: "application/json" },
cache: "no-store",
credentials: "omit"
});
POST /api/import
Создаёт новую запись encrypted vault и индексы доступа. Браузер шифрует минимальный payload до запроса; API не получает пароль Steam, session, identity secret или plaintext shared secret.
Тело запроса
payload object: encrypted payload хранилища, { v: int, iv: string, ciphertext: string }.
primary object: основной индекс доступа, { token: string, wrap: object }.
primary.wrap object: wrapped data key, { v: int, salt: string, iv: string, ciphertext: string }.
alias object|null: опциональный пользовательский код с той же структурой, что и primary.
Ответ и ошибки
201 возвращает recordId, createdAt и aliasAttached. Частые ошибки: 400 некорректный envelope, 409 занятый token, 413 тело больше 24 KB, 415 нет JSON content type, 429 превышен лимит импорта.
await fetch("/api/import", {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "omit",
body: JSON.stringify({
payload: { v: 1, iv: "...", ciphertext: "..." },
primary: {
token: "base64url-lookup-token",
wrap: { v: 1, salt: "...", iv: "...", ciphertext: "..." }
},
alias: null
})
});
POST /api/lookup
Находит encrypted vault по заранее подготовленному lookup token и возвращает wrapped data key для этого access code. Расшифровка по-прежнему происходит только в браузере.
Тело запроса
token string - 43-символьный base64url lookup token, локально полученный из основного ID или alias.
Ответ и ошибки
200 возвращает kind, recordId, payload, wrap, createdAt и hasAlias. 404 означает неизвестный token. 503 может кратко появиться, пока KV реплицирует запись.
const response = await fetch("/api/lookup", {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "omit",
body: JSON.stringify({ token: lookupToken })
});
POST /api/alias
Создаёт, заменяет или удаляет пользовательский код для существующего vault. Endpoint намеренно требует primary token: вход по alias не даёт права управлять alias.
Тело запроса
primaryToken string: primary lookup token текущего vault.
alias object: для добавления/замены, { token: string, wrap: object }.
alias.wrap object: wrapped key, { v: int, salt: string, iv: string, ciphertext: string }.
remove boolean: отправьте true, чтобы удалить alias вместо добавления.
Ответ и ошибки
200 возвращает aliasAttached: true после добавления/замены и false после удаления. 403 означает, что token не primary; 409 - alias уже используется другим vault.
await fetch("/api/alias", {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "omit",
body: JSON.stringify({
primaryToken: "...",
alias: {
token: "...",
wrap: { v: 1, salt: "...", iv: "...", ciphertext: "..." }
}
})
});
POST /api/delete
Безвозвратно удаляет encrypted vault и все связанные индексы доступа из KV. Используется при удалении аккаунта или отзыве доверия к сохранённому ID.
Тело запроса
primaryToken string обязателен. Alias token отклоняется, потому что удаление относится к управлению хранилищем.
Ответ и ошибки
200 возвращает { "ok": true, "deleted": true }. 403 означает, что token не primary. 404 означает, что vault уже отсутствует.
await fetch("/api/delete", {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "omit",
body: JSON.stringify({ primaryToken: "..." })
});
POST /api/save-saved
Сохраняет серверную часть saved profile. При PIN-защите браузер хранит локально только metadata профиля, а encrypted access ID остаётся в KV за verifier.
Тело запроса
id string: browser-generated saved profile id, 24-64 base64url символа.
accessToken string: lookup token vault, который открывает этот профиль.
verifier string: 43-символьный verifier, полученный в браузере.
encrypted object: encrypted access ID envelope, { v: int, salt: string, iv: string, ciphertext: string }.
Ответ и ошибки
200 возвращает { "ok": true, "saved": true }. 404 означает, что связанный vault не найден. 409 - этот profile id уже указывает на другой vault.
await fetch("/api/save-saved", {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "omit",
body: JSON.stringify({
id: "profile-id",
accessToken: "...",
verifier: "...",
encrypted: { v: 1, salt: "...", iv: "...", ciphertext: "..." }
})
});
POST /api/open-saved
Открывает encrypted access ID сохранённого профиля после того, как браузер получил verifier из введённого PIN или device secret. API проверяет verifier, но не может расшифровать access ID.
Тело запроса
id string определяет saved profile; verifier string подтверждает, что пользователь ввёл подходящий PIN или использует сохранённый device secret.
Ответ и ошибки
200 возвращает encrypted и сбрасывает счётчик ошибок. 403 возвращает attemptsLeft; после 5 неверных попыток также возвращает deleted: true и удаляет vault из KV.
const response = await fetch("/api/open-saved", {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "omit",
body: JSON.stringify({ id: "profile-id", verifier: "..." })
});
POST /api/delete-saved
Удаляет saved profile record и vault, связанный с access token этого профиля. Интерфейс вызывает endpoint при удалении запомненного аккаунта.
Тело запроса
id string - saved profile id, который хранится в браузерном списке профилей.
Ответ и ошибки
200 возвращает deleted: true, если связанный vault удалён, и false, если saved profile уже отсутствовал. Операция идемпотентна для отсутствующих saved profiles.
await fetch("/api/delete-saved", {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "omit",
body: JSON.stringify({ id: "profile-id" })
});