openapi: 3.0.3
info:
title: Obscura Server API
description: |
A privacy-first messaging relay.
- **Control Plane (REST):** Registration, Key Management, Sending.
- **Data Plane (WebSocket):** Real-time message delivery and acknowledgement.
- **Protocol:** Protocol Buffers (application/x-protobuf) are used for message bodies to minimize metadata leakage and binary bloat.
version: 0.0.0
paths:
/v1/users:
post:
summary: Register a new user device.
tags: [Account]
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RegistrationRequest'
responses:
'201':
description: Account created.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthResponse'
'400':
description: Invalid input or username already exists.
/v1/sessions:
post:
summary: Login and retrieve a session token.
tags: [Account]
requestBody:
content:
application/json:
schema:
type: object
properties:
username:
type: string
password:
type: string
required:
- username
- password
responses:
'200':
description: Authenticated.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthResponse'
'401':
description: Unauthorized (Invalid credentials).
delete:
summary: Logout (Revoke Refresh Token).
description: |
Revokes the provided Refresh Token, effectively logging the user out of that session.
tags: [Account]
requestBody:
content:
application/json:
schema:
type: object
required: [refreshToken]
properties:
refreshToken:
type: string
responses:
'200':
description: Session revoked.
/v1/sessions/refresh:
post:
summary: Refresh Access Token.
description: |
Exchanges a valid Refresh Token for a new pair (Access Token + Refresh Token).
Implements Rotation: The old Refresh Token is invalidated immediately.
tags: [Account]
requestBody:
content:
application/json:
schema:
type: object
required: [refreshToken]
properties:
refreshToken:
type: string
responses:
'200':
description: Tokens refreshed.
content:
application/json:
schema:
$ref: '#/components/schemas/AuthResponse'
'401':
description: Unauthorized (Invalid or expired refresh token).
/v1/keys:
post:
summary: Upload PreKeys or Perform Device Takeover.
description: |
Uploads new Signed PreKeys and One-Time PreKeys.
**Device Takeover:**
If an `identityKey` is provided and it *differs* from the stored key for this user:
- Replaces the Identity Key.
- Deletes ALL old keys (Signed and One-Time).
- Deletes ALL pending messages.
- Disconnects active WebSockets.
If `identityKey` matches the stored key or is omitted, it acts as a standard key refill (appending new keys).
tags: [Keys]
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PreKeyUpload'
responses:
'200':
description: Keys updated or takeover successful.
'400':
description: Invalid key formats or missing registrationId for takeover.
'401':
description: Unauthorized.
/v1/keys/{userId}:
get:
summary: Fetch PreKey Bundle.
description: |
Returns active PreKey bundle for the target user.
Server atomically consumes one One-Time PreKey (if available).
tags: [Keys]
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: PreKey Bundle.
content:
application/json:
schema:
$ref: '#/components/schemas/PreKeyBundle'
'404':
description: User not found or no keys available.
/v1/messages/{recipientId}:
post:
summary: Send an encrypted message (Push).
description: |
Pushes an encrypted envelope to the target device's queue.
**Payload:** `OutgoingMessage` (Protobuf).
The recipient will receive this via WebSocket.
tags: [Messaging]
security:
- bearerAuth: []
parameters:
- name: recipientId
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/x-protobuf:
schema:
type: string
format: binary
description: Serialized `OutgoingMessage` protobuf.
application/octet-stream:
schema:
type: string
format: binary
description: Serialized `OutgoingMessage` protobuf.
responses:
'201':
description: Message queued.
'400':
description: Invalid Protobuf.
'401':
description: Unauthorized.
'404':
description: Recipient not found.
/v1/gateway:
get:
summary: Connect to the Message Stream.
description: |
**WebSocket Endpoint.**
Clients must connect here to receive messages.
- **Protocol:** `WebSocketFrame` (Protobuf).
- **Auth:** Pass the JWT in the query string: `ws://.../v1/gateway?token=<jwt>`.
- **Flow:** Server pushes `IncomingEnvelope`. Client responds with `AckMessage`. Server deletes message.
tags: [Messaging]
parameters:
- name: token
in: query
required: true
schema:
type: string
description: JWT Access Token.
responses:
'101':
description: Switching Protocols.
'401':
description: Unauthorized (Invalid or missing token).
/v1/attachments:
post:
summary: Upload an attachment.
description: |
Uploads an encrypted binary blob to long-term storage (S3).
Blob will be auto-deleted after the configured TTL.
tags: [Attachments]
security:
- bearerAuth: []
requestBody:
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'201':
description: Upload successful.
content:
application/json:
schema:
type: object
properties:
id:
type: string
format: uuid
expiresAt:
type: integer
format: int64
description: UNIX timestamp of when the file will be deleted.
'400':
description: File too large.
'401':
description: Unauthorized.
/v1/attachments/{id}:
get:
summary: Download an attachment.
tags: [Attachments]
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Binary file stream.
content:
application/octet-stream:
schema:
type: string
format: binary
'401':
description: Unauthorized.
'404':
description: Not found or expired.
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
AuthResponse:
type: object
required:
- token
- refreshToken
- expiresAt
properties:
token:
type: string
description: Short-lived JWT Access Token.
refreshToken:
type: string
description: Long-lived opaque Refresh Token.
expiresAt:
type: integer
format: int64
description: UNIX timestamp of access token expiration.
RegistrationRequest:
type: object
required:
- username
- password
- identityKey
- registrationId
- signedPreKey
- oneTimePreKeys
properties:
username:
type: string
password:
type: string
identityKey:
type: string
description: Base64 encoded public identity key.
registrationId:
type: integer
signedPreKey:
$ref: '#/components/schemas/SignedPreKeyDto'
oneTimePreKeys:
type: array
items:
$ref: '#/components/schemas/OneTimePreKeyDto'
PreKeyUpload:
type: object
required:
- signedPreKey
- oneTimePreKeys
properties:
identityKey:
type: string
description: Optional Base64 Identity Key. Triggers takeover if changed.
registrationId:
type: integer
description: Required if identityKey is provided.
signedPreKey:
$ref: '#/components/schemas/SignedPreKeyDto'
oneTimePreKeys:
type: array
items:
$ref: '#/components/schemas/OneTimePreKeyDto'
PreKeyBundle:
type: object
properties:
registrationId:
type: integer
identityKey:
type: string
format: byte
signedPreKey:
$ref: '#/components/schemas/SignedPreKeyDto'
oneTimePreKey:
$ref: '#/components/schemas/OneTimePreKeyDto'
nullable: true
SignedPreKeyDto:
type: object
required:
- keyId
- publicKey
- signature
properties:
keyId:
type: integer
publicKey:
type: string
description: Base64 encoded public key
signature:
type: string
description: Base64 encoded signature
OneTimePreKeyDto:
type: object
required:
- keyId
- publicKey
properties:
keyId:
type: integer
publicKey:
type: string
description: Base64 encoded public key