| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026 |
- openapi: 3.1.0
- info:
- title: Celestia Trace iOS Remote API
- version: 2026-07-24
- description: |
- iOS 客户端实际调用的远程接口契约。
- 维护规则:
- 1. backend 路由、请求字段或响应字段变化时,先更新本文件,再更新 Swift 模型。
- 2. 所有 JSON 接口使用统一响应 `{code, message, data}`。
- 3. 除登录、注册和刷新令牌外,接口均使用 Bearer access token。
- 4. 公网网关保留 `/celestia-trace/v1`;后端 Gin 路由本身使用 `/v1`。
- 5. 服务端时间使用 RFC 3339 / ISO 8601,客户端同时兼容带或不带小数秒。
- x-client-sources:
- - CelestiaTrace/Services/Network/APIClient.swift
- - CelestiaTrace/Services/Network/RemoteNetworkService.swift
- - CelestiaTrace/Services/Auth/RemoteAuthService.swift
- servers:
- - url: https://api.ccdw.life/celestia-trace/v1
- description: Production
- tags:
- - name: Health
- - name: Auth
- - name: User
- - name: Devices
- - name: Sessions
- - name: Assets
- - name: Sync
- paths:
- /health:
- get:
- tags: [Health]
- operationId: getHealth
- security: []
- responses:
- "200":
- description: API and database are healthy
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/HealthEnvelope"
- "503":
- $ref: "#/components/responses/Error"
- /auth/register:
- post:
- tags: [Auth]
- operationId: register
- security: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/RegisterRequest"
- responses:
- "200":
- description: Account and login session created
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/AuthEnvelope"
- "400":
- $ref: "#/components/responses/Error"
- "409":
- $ref: "#/components/responses/Error"
- /auth/login:
- post:
- tags: [Auth]
- operationId: login
- security: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/LoginRequest"
- responses:
- "200":
- description: Login session created
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/AuthEnvelope"
- "400":
- $ref: "#/components/responses/Error"
- "401":
- $ref: "#/components/responses/Error"
- /auth/refresh:
- post:
- tags: [Auth]
- operationId: refreshAccessToken
- security: []
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required: [refreshToken]
- properties:
- refreshToken:
- type: string
- responses:
- "200":
- description: Tokens rotated; the old refresh token is no longer valid
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/AuthEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- /auth/logout:
- post:
- tags: [Auth]
- operationId: logout
- responses:
- "200":
- $ref: "#/components/responses/EmptySuccess"
- "401":
- $ref: "#/components/responses/Error"
- /user/profile:
- get:
- tags: [User]
- operationId: getProfile
- responses:
- "200":
- description: Current user
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/UserEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- put:
- tags: [User]
- operationId: updateProfile
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/UpdateProfileRequest"
- responses:
- "200":
- description: Updated user
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/UserEnvelope"
- "400":
- $ref: "#/components/responses/Error"
- "401":
- $ref: "#/components/responses/Error"
- "409":
- $ref: "#/components/responses/Error"
- /user/change-password:
- post:
- tags: [User]
- operationId: changePassword
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required: [oldPassword, newPassword]
- properties:
- oldPassword:
- type: string
- newPassword:
- type: string
- minLength: 6
- responses:
- "200":
- $ref: "#/components/responses/EmptySuccess"
- "400":
- $ref: "#/components/responses/Error"
- "401":
- $ref: "#/components/responses/Error"
- /devices:
- post:
- tags: [Devices]
- operationId: bindDevice
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/BindDeviceRequest"
- responses:
- "200":
- description: Bound or updated cloud device
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/DeviceEnvelope"
- "400":
- $ref: "#/components/responses/Error"
- "401":
- $ref: "#/components/responses/Error"
- /devices/{deviceId}:
- parameters:
- - $ref: "#/components/parameters/DeviceId"
- put:
- tags: [Devices]
- operationId: updateDevice
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/UpdateDeviceRequest"
- responses:
- "200":
- description: Updated cloud device
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/DeviceEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- "404":
- $ref: "#/components/responses/Error"
- delete:
- tags: [Devices]
- operationId: unbindDevice
- responses:
- "200":
- description: Device unbound
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/DeleteEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- "404":
- $ref: "#/components/responses/Error"
- /sessions:
- get:
- tags: [Sessions]
- operationId: listSessions
- parameters:
- - name: includeDeleted
- in: query
- schema:
- type: boolean
- default: true
- description: iOS uses true to receive deletion tombstones.
- responses:
- "200":
- description: All sessions visible to the current user
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/SessionListEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- post:
- tags: [Sessions]
- operationId: upsertSession
- description: |
- Idempotent upsert by `(userId, clientId)`.
- Existing sessions must send `baseRevision`; a mismatch returns HTTP/code 409.
- `deletedEventClientIds` removes events deleted locally since the last pull.
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/SessionUpsertRequest"
- responses:
- "200":
- description: Created or updated session
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/SessionEnvelope"
- "400":
- $ref: "#/components/responses/Error"
- "401":
- $ref: "#/components/responses/Error"
- "409":
- description: Optimistic-lock conflict
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/ConflictEnvelope"
- /sessions/{sessionId}/assets:
- parameters:
- - $ref: "#/components/parameters/SessionId"
- post:
- tags: [Assets]
- operationId: uploadAsset
- description: |
- Used for files smaller than 16 MiB. `clientId` makes retries idempotent.
- A new upload returns `{asset, sessionRevision}`. An idempotent replay may
- return the asset directly; the iOS decoder intentionally accepts both.
- requestBody:
- required: true
- content:
- multipart/form-data:
- schema:
- type: object
- required: [clientId, kind, file]
- properties:
- clientId:
- type: string
- kind:
- $ref: "#/components/schemas/AssetKind"
- file:
- type: string
- format: binary
- responses:
- "200":
- description: Uploaded asset or idempotent existing asset
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/AssetUploadEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- "404":
- $ref: "#/components/responses/Error"
- "413":
- $ref: "#/components/responses/QuotaError"
- /sessions/{sessionId}/assets/init:
- parameters:
- - $ref: "#/components/parameters/SessionId"
- post:
- tags: [Assets]
- operationId: initializeChunkedUpload
- description: iOS uses this flow for files at least 16 MiB, with 8 MiB chunks.
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/ChunkUploadInitRequest"
- responses:
- "200":
- description: Chunked upload initialized
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/ChunkUploadInitEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- "404":
- $ref: "#/components/responses/Error"
- "413":
- $ref: "#/components/responses/QuotaError"
- /sessions/{sessionId}/assets/chunk:
- parameters:
- - $ref: "#/components/parameters/SessionId"
- post:
- tags: [Assets]
- operationId: uploadChunk
- requestBody:
- required: true
- content:
- multipart/form-data:
- schema:
- type: object
- required: [uploadId, chunkIndex, file]
- properties:
- uploadId:
- type: string
- format: uuid
- chunkIndex:
- type: integer
- minimum: 0
- file:
- type: string
- format: binary
- responses:
- "200":
- description: Chunk accepted
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/ChunkProgressEnvelope"
- "400":
- $ref: "#/components/responses/Error"
- "401":
- $ref: "#/components/responses/Error"
- "404":
- $ref: "#/components/responses/Error"
- "410":
- $ref: "#/components/responses/Error"
- /sessions/{sessionId}/assets/complete:
- parameters:
- - $ref: "#/components/parameters/SessionId"
- post:
- tags: [Assets]
- operationId: completeChunkedUpload
- requestBody:
- required: true
- content:
- application/json:
- schema:
- type: object
- required: [uploadId, totalChunks]
- properties:
- uploadId:
- type: string
- format: uuid
- totalChunks:
- type: integer
- minimum: 1
- responses:
- "200":
- description: Chunks merged and asset created
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/AssetUploadEnvelope"
- "400":
- $ref: "#/components/responses/Error"
- "401":
- $ref: "#/components/responses/Error"
- "404":
- $ref: "#/components/responses/Error"
- "410":
- $ref: "#/components/responses/Error"
- /sessions/{sessionId}/assets/{assetId}:
- parameters:
- - $ref: "#/components/parameters/SessionId"
- - $ref: "#/components/parameters/AssetId"
- get:
- tags: [Assets]
- operationId: downloadAsset
- responses:
- "200":
- description: Attachment bytes; Content-Disposition contains the original filename
- content:
- application/octet-stream:
- schema:
- type: string
- format: binary
- "401":
- $ref: "#/components/responses/Error"
- "404":
- $ref: "#/components/responses/Error"
- /storage/quota:
- get:
- tags: [Sync]
- operationId: getStorageQuota
- description: Called before uploading the pending assets for a session.
- responses:
- "200":
- description: Current storage quota in bytes
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/StorageQuotaEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- /sync/trigger:
- post:
- tags: [Sync]
- operationId: recordSyncCheckpoint
- description: Called only after all session metadata and assets finish syncing.
- responses:
- "200":
- description: Client sync checkpoint recorded
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/SyncCheckpointEnvelope"
- "401":
- $ref: "#/components/responses/Error"
- components:
- securitySchemes:
- bearerAuth:
- type: http
- scheme: bearer
- bearerFormat: JWT
- parameters:
- DeviceId:
- name: deviceId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- SessionId:
- name: sessionId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- AssetId:
- name: assetId
- in: path
- required: true
- schema:
- type: string
- format: uuid
- responses:
- EmptySuccess:
- description: Success without a data payload
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/EmptyEnvelope"
- Error:
- description: Request failed
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/ErrorEnvelope"
- QuotaError:
- description: Storage quota exceeded
- content:
- application/json:
- schema:
- $ref: "#/components/schemas/QuotaErrorEnvelope"
- schemas:
- EnvelopeBase:
- type: object
- required: [code, message]
- properties:
- code:
- type: integer
- message:
- type: string
- EmptyEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- properties:
- data:
- type: "null"
- ErrorEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- properties:
- data: {}
- HealthEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- type: string
- const: healthy
- RegisterRequest:
- type: object
- required: [username, identifier, password]
- properties:
- username:
- type: string
- minLength: 2
- identifier:
- type: string
- description: Email address or phone number.
- password:
- type: string
- minLength: 6
- LoginRequest:
- type: object
- required: [identifier, password]
- properties:
- identifier:
- type: string
- description: Username, email address, or phone number.
- password:
- type: string
- UpdateProfileRequest:
- type: object
- properties:
- username:
- type: [string, "null"]
- email:
- type: [string, "null"]
- phoneNumber:
- type: [string, "null"]
- avatarURL:
- type: [string, "null"]
- User:
- type: object
- required: [id, username, registeredAt]
- properties:
- id:
- type: string
- format: uuid
- username:
- type: string
- email:
- type: string
- phoneNumber:
- type: string
- avatarURL:
- type: string
- format: uri
- registeredAt:
- type: string
- format: date-time
- updatedAt:
- type: string
- format: date-time
- AuthData:
- type: object
- required: [user, token, refreshToken, expiresAt]
- properties:
- user:
- $ref: "#/components/schemas/User"
- token:
- type: string
- description: Short-lived access JWT.
- refreshToken:
- type: string
- description: Rotated on every refresh.
- expiresAt:
- type: string
- format: date-time
- AuthEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- $ref: "#/components/schemas/AuthData"
- UserEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- $ref: "#/components/schemas/User"
- BindDeviceRequest:
- type: object
- required: [name]
- properties:
- name:
- type: string
- peripheralUUID:
- type: string
- hardwareMAC:
- type: [string, "null"]
- firmwareVersion:
- type: [string, "null"]
- batteryLevel:
- type: [integer, "null"]
- minimum: 0
- maximum: 100
- freeStorageMB:
- type: [integer, "null"]
- totalStorageMB:
- type: [integer, "null"]
- UpdateDeviceRequest:
- type: object
- properties:
- name:
- type: [string, "null"]
- batteryLevel:
- type: [integer, "null"]
- minimum: 0
- maximum: 100
- isConnected:
- type: [boolean, "null"]
- firmwareVersion:
- type: [string, "null"]
- freeStorageMB:
- type: [integer, "null"]
- totalStorageMB:
- type: [integer, "null"]
- Device:
- type: object
- required: [id, name, peripheralUUID]
- properties:
- id:
- type: string
- format: uuid
- userId:
- type: string
- format: uuid
- name:
- type: string
- peripheralUUID:
- type: string
- hardwareMAC:
- type: string
- batteryLevel:
- type: integer
- firmwareVersion:
- type: string
- freeStorageMB:
- type: integer
- totalStorageMB:
- type: integer
- isConnected:
- type: boolean
- boundAt:
- type: string
- format: date-time
- updatedAt:
- type: string
- format: date-time
- DeviceEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- $ref: "#/components/schemas/Device"
- EventUpsert:
- type: object
- required: [clientId, relativeTimeMs, eventType]
- properties:
- clientId:
- type: string
- relativeTimeMs:
- type: integer
- format: int64
- eventType:
- type: string
- enum: [PHOTO, NOTE, MARKER, VOICE, CONTINUATION]
- textContent:
- type: [string, "null"]
- voiceStartOffsetMs:
- type: [integer, "null"]
- format: int64
- voiceEndOffsetMs:
- type: [integer, "null"]
- format: int64
- SessionUpsertRequest:
- type: object
- required: [clientId, title, startTime, durationMs, events, deletedEventClientIds]
- properties:
- clientId:
- type: string
- title:
- type: string
- startTime:
- type: string
- format: date-time
- endTime:
- type: [string, "null"]
- format: date-time
- durationMs:
- type: integer
- format: int64
- events:
- type: array
- items:
- $ref: "#/components/schemas/EventUpsert"
- baseRevision:
- type: [integer, "null"]
- format: int64
- deletedEventClientIds:
- type: array
- items:
- type: string
- RemoteEvent:
- allOf:
- - $ref: "#/components/schemas/EventUpsert"
- - type: object
- required: [id]
- properties:
- id:
- type: string
- format: uuid
- clientId:
- type: [string, "null"]
- createdAt:
- type: string
- format: date-time
- RemoteSession:
- type: object
- required: [id, title, startTime, durationMs, revision]
- properties:
- id:
- type: string
- format: uuid
- clientId:
- type: [string, "null"]
- title:
- type: string
- startTime:
- type: string
- format: date-time
- endTime:
- type: [string, "null"]
- format: date-time
- durationMs:
- type: integer
- format: int64
- revision:
- type: integer
- format: int64
- deletedAt:
- type: [string, "null"]
- format: date-time
- events:
- type: array
- items:
- $ref: "#/components/schemas/RemoteEvent"
- assets:
- type: array
- items:
- $ref: "#/components/schemas/Asset"
- SessionEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- $ref: "#/components/schemas/RemoteSession"
- SessionListEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- type: array
- items:
- $ref: "#/components/schemas/RemoteSession"
- ConflictEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- type: object
- required: [serverRevision]
- properties:
- serverRevision:
- type: integer
- format: int64
- AssetKind:
- type: string
- enum: [AUDIO, PHOTO]
- Asset:
- type: object
- required: [id, clientId, kind, fileName, mimeType, sizeBytes, sha256]
- properties:
- id:
- type: string
- format: uuid
- clientId:
- type: string
- kind:
- $ref: "#/components/schemas/AssetKind"
- fileName:
- type: string
- mimeType:
- type: string
- sizeBytes:
- type: integer
- format: int64
- sha256:
- type: string
- pattern: "^[a-fA-F0-9]{64}$"
- createdAt:
- type: string
- format: date-time
- AssetUploadData:
- type: object
- required: [asset, sessionRevision]
- properties:
- asset:
- $ref: "#/components/schemas/Asset"
- sessionRevision:
- type: integer
- format: int64
- AssetUploadEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- oneOf:
- - $ref: "#/components/schemas/AssetUploadData"
- - $ref: "#/components/schemas/Asset"
- ChunkUploadInitRequest:
- type: object
- required: [clientId, kind, fileName, mimeType, fileSize, chunkSize]
- properties:
- clientId:
- type: string
- kind:
- $ref: "#/components/schemas/AssetKind"
- fileName:
- type: string
- mimeType:
- type: string
- fileSize:
- type: integer
- format: int64
- minimum: 1
- chunkSize:
- type: integer
- minimum: 1
- ChunkUploadInitEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- type: object
- required: [uploadId, totalChunks, chunkSize, expiresAt]
- properties:
- uploadId:
- type: string
- format: uuid
- totalChunks:
- type: integer
- chunkSize:
- type: integer
- expiresAt:
- type: string
- format: date-time
- ChunkProgressEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- type: object
- required: [uploadId, chunkIndex, uploadedChunks, totalChunks]
- properties:
- uploadId:
- type: string
- format: uuid
- chunkIndex:
- type: integer
- uploadedChunks:
- type: integer
- totalChunks:
- type: integer
- StorageQuota:
- type: object
- required: [totalBytes, usedBytes, remainingBytes]
- properties:
- totalBytes:
- type: integer
- format: int64
- usedBytes:
- type: integer
- format: int64
- remainingBytes:
- type: integer
- format: int64
- StorageQuotaEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- $ref: "#/components/schemas/StorageQuota"
- QuotaErrorEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- allOf:
- - $ref: "#/components/schemas/StorageQuota"
- - type: object
- required: [requiredBytes]
- properties:
- requiredBytes:
- type: integer
- format: int64
- SyncCheckpointEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- type: object
- required: [syncedAt, message]
- properties:
- syncedAt:
- type: string
- format: date-time
- message:
- type: string
- DeleteEnvelope:
- allOf:
- - $ref: "#/components/schemas/EnvelopeBase"
- - type: object
- required: [data]
- properties:
- data:
- type: object
- required: [deletedId]
- properties:
- deletedId:
- type: string
- security:
- - bearerAuth: []
|