- GET /v1/capabilities and the extended sync entity allowlist (§0, §1). - Shared ledgers (§7): ledgers, members and hashed one-time invite codes; X-Balance-Ledger selects the space on /v1/sync and /v1/attachments with its own records, change stream and cursor. Personal space keeps the v1 tables and behaviour; the migration only adds tables. - Attachments (§8): SHA-256, size and media type checks, blob files in BALANCE_ATTACHMENTS_DIR (default: attachments next to the database), garbage collection of unreferenced blobs with a grace period for fresh uploads, and ledger deletion removes its blobs. - Tests for every new endpoint and error code; API, sync, data model, security, deployment and README docs updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8.2 KiB
HTTP API
Общие правила
API предназначен для iOS/macOS/watchOS и Android Balance.
Базовый URL задаётся клиентом и зависит от deployment environment.
JSON:
Content-Type: application/json
Защищённые endpoints используют:
Authorization: Bearer <access-token>
Authentication
Основные операции:
POST /v1/auth/register
POST /v1/auth/login
POST /v1/auth/logout
POST /v1/auth/refresh
Register
Создаёт пользовательский аккаунт.
Input должен валидироваться сервером. Password никогда не хранится plaintext.
Login
Проверяет credentials и создаёт authenticated session.
Refresh
Принимает refresh credential и выдаёт новый access credential согласно текущей server-side session policy.
Logout
Инвалидирует текущую session/refresh state согласно текущей реализации.
Synchronization
Основной endpoint:
POST /v1/sync
Логический request:
{
"cursor": 123,
"deviceId": "UUID",
"changes": [],
"pull": true
}
Точная JSON schema должна оставаться совместимой с ServerSync.swift и Android SyncRepository.
Change
Логическая форма:
{
"entity": "transaction",
"id": "UUID",
"deleted": false,
"updatedAt": "2026-09-08T10:00:00Z",
"payload": {}
}
Поддерживаемые entity (контракт v2, docs/CONTRACT_V2.md §1):
transaction category budget account transfer recurring
goal debt rate setting attachment
Незнакомая entity отклоняется 400 invalid_change; текст сообщения
(entity must be transaction, category, or budget) сохранён из v1, потому что
он записан в эталоне tools/compatibility_expected.txt. Payload сервер хранит
как есть и не валидирует по сущности.
Заголовок X-Balance-Ledger: <id> выбирает общее пространство (см. ниже). Без
заголовка (или с пустым значением) — личное пространство, как в v1.
Push
Client отправляет локальные изменения.
Server должен:
- аутентифицировать пользователя;
- проверить ownership/ID;
- валидировать payload;
- применить изменения;
- создать server change entries;
- вернуть результат и актуальную sync metadata.
Операция должна быть безопасна при retry.
Pull
Client передаёт cursor.
Server возвращает изменения после этого cursor, включая deletions.
Если существует pagination, ответ должен содержать понятный признак продолжения и новый cursor/sequence.
Контракт v2
Capabilities
GET /v1/capabilities — без авторизации:
{"contract":2,
"entities":["transaction","category","budget","account","transfer","recurring","goal","debt","rate","setting","attachment"],
"attachments":{"maxBytes":10485760,"types":["image/jpeg","image/png","image/heic","application/pdf"]},
"ledgers":true}
Общие пространства (ledgers)
Все запросы — с Authorization. Объект ledger:
{"id","name","role","members":[{"userId","email","role"}]}, role —
роль текущего пользователя (owner | member), владелец идёт первым в members.
| Метод | Путь | Успех | Кто |
|---|---|---|---|
| GET | /v1/ledgers |
200 {"ledgers":[…]} (только общие) |
любой |
| POST | /v1/ledgers {"name"} |
201 ledger, role:"owner" |
любой |
| PATCH | /v1/ledgers/{id} {"name"} |
200 ledger |
владелец |
| DELETE | /v1/ledgers/{id} |
204 |
владелец; удаляет записи, курсор, приглашения, участников и файлы вложений |
| POST | /v1/ledgers/{id}/invites |
201 {"code","expiresAt"} |
владелец |
| POST | /v1/ledgers/join {"code"} |
200 ledger, role:"member" |
любой |
| DELETE | /v1/ledgers/{id}/members/{userId} |
204 |
владелец — любого участника; участник — себя |
- Имя: после
trim1–100 символов без управляющих → иначе400 invalid_name. - Код приглашения: 10 символов из
ABCDEFGHJKLMNPQRSTUVWXYZ23456789, живёт 7 дней, одноразовый. При вводе регистр, пробелы и-игнорируются. Повторный join участника возвращает ledger и код не расходует./v1/ledgers/joinвходит в общий лимит 30 запросов/мин на IP вместе с auth endpoints. - Ошибки:
400 invalid_code(неверный, использованный или просроченный код),403 forbidden(не участник или не владелец),404 not_found(нет такого пространства или участника),409 owner_cannot_leave(владелец пытается выйти — он может только удалить пространство),400 invalid_json. X-Balance-Ledgerна/v1/syncи/v1/attachments/*: не участник →403 forbidden, несуществующее →404 not_found.
Вложения
| Метод | Путь | Ответ |
|---|---|---|
| PUT | /v1/attachments/{sha256} |
тело — байты; 201 первый раз, 200 повтор; {"sha256","size","contentType"} |
| GET/HEAD | /v1/attachments/{sha256} |
200, байты, сохранённый Content-Type, Content-Disposition: attachment; 404 not_found, если в пространстве нет файла |
{sha256}— 64 hex-символа в нижнем регистре; SHA-256 тела должен совпасть, иначе400 hash_mismatch(так же для неверного формата хеша в PUT).Content-Type(параметры игнорируются) —image/jpeg,image/png,image/heic,application/pdf, иначе415 unsupported_media_type.- Больше 10 MiB →
413 payload_too_large. Лимит тела 10 MiB действует только для авторизованного PUT; остальные запросы по-прежнему ограничены 2 MiB.
CORS
Ответы v1 сохраняют заголовки эталона Go. Для маршрутов v2 и для настоящих
preflight-запросов (есть Access-Control-Request-Method) разрешённые origin
получают Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
и Access-Control-Allow-Headers: Authorization, Content-Type, X-Balance-Ledger.
Ошибки
Рекомендуемый контракт:
{
"error": {
"code": "invalid_request",
"message": "Human-readable safe message"
}
}
Не возвращать:
- SQL error;
- stack trace;
- password/token;
- filesystem paths;
- внутренние identifiers инфраструктуры.
HTTP status semantics
Рекомендуемая семантика:
200 OK — успешный request
201 Created — создание ресурса
400 Bad Request — malformed/invalid input
401 Unauthorized — missing/expired auth
403 Forbidden — resource belongs to another user
404 Not Found — resource/endpoint unavailable
409 Conflict — semantic conflict, если endpoint использует такую семантику
429 Too Many Requests — rate limit
500 Internal Server Error — internal failure
Конкретные status codes текущей реализации имеют приоритет над этой общей рекомендацией.