Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 203 additions & 14 deletions backend/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "JWT issued by POST /auth/login or POST /auth/refresh."
"description": "JWT issued by POST /auth/login or POST /auth/refresh. `exp` is enforced with **zero** clock tolerance β€” a token is rejected the moment it expires. Only `nbf`/`iat` get a 5s tolerance for clock skew. The revocation list is checked on every authenticated request, so a token presented after `POST /auth/logout` (401 `TOKEN_REVOKED`) or after `POST /auth/logout-all` is refused immediately rather than at expiry."
},
"apiKeyAuth": {
"type": "apiKey",
Expand All @@ -54,6 +54,31 @@
"type": "string",
"format": "uuid"
}
},
"pageSize": {
"name": "limit",
"in": "query",
"required": false,
"description": "Maximum number of items to return. Hard ceiling is 50 (default 20). A larger value is **rejected**, not clamped, with `400` and `code: \"LIMIT_EXCEEDED\"` β€” the response body repeats the ceiling so clients can self-correct. Use `page` (clamped to 1..1000) to walk the rest.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 50,
"default": 20,
"example": 20
}
},
"pageNumber": {
"name": "page",
"in": "query",
"required": false,
"description": "1-based page number for offset pagination. Values outside 1..1000 are clamped rather than rejected, and the effective page is echoed back in `pagination.currentPage`.",
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 1000,
"default": 1
}
}
},
"schemas": {
Expand Down Expand Up @@ -106,6 +131,10 @@
"count": {
"type": "integer"
},
"limit": {
"type": "integer",
"maximum": 50
},
"total": {
"type": "integer"
},
Expand All @@ -118,7 +147,9 @@
"nullable": true
},
"currentPage": {
"type": "integer"
"type": "integer",
"minimum": 1,
"maximum": 1000
},
"totalPages": {
"type": "integer"
Expand All @@ -131,6 +162,62 @@
}
}
},
"Vault": {
"type": "object",
"required": [
"id",
"aum",
"tvlUsd",
"createdAt",
"updatedAt"
],
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"aum": {
"type": "number",
"example": 0
},
"tvlUsd": {
"type": "string",
"nullable": true,
"example": "1250000.00"
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"VaultListResponse": {
"type": "object",
"required": [
"data",
"pagination",
"timestamp"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Vault"
}
},
"pagination": {
"$ref": "#/components/schemas/PaginationMeta"
},
"timestamp": {
"type": "string",
"format": "date-time"
}
}
},
"VaultSummary": {
"type": "object",
"properties": {
Expand All @@ -143,15 +230,8 @@
"example": 0
},
"apy": {
"type": ["number", "null"],
"example": 8.45,
"description": "Annualised APY as a decimal percentage. null when the vault has insufficient price history (e.g. zero shares or fewer than 2 snapshots)."
},
"apyStatus": {
"type": "string",
"enum": ["ok", "insufficient_data"],
"example": "ok",
"description": "ok when apy is a valid number; insufficient_data when apy is null."
"type": "number",
"example": 0
},
"timestamp": {
"type": "string",
Expand Down Expand Up @@ -363,11 +443,21 @@
"timestamp": "2024-01-01T00:00:00.000Z",
"uptime": 123.4,
"environment": "production",
"lastIndexedLedger": 12345678,
"checks": {
"api": "up",
"cache": "up",
"stellarRpc": "up",
"databasePrimary": "up",
"databaseReplica": "up",
"prisma": "up",
"jobs": "up",
"indexer": "up"
},
"sorobanCircuitBreaker": {
"state": "closed",
"failures": 0,
"retryAfterMs": 0
}
}
}
Expand Down Expand Up @@ -522,16 +612,26 @@
"application/json": {
"schema": {
"type": "object",
"required": ["apy", "apyStatus", "timestamp"],
"required": [
"apy",
"apyStatus",
"timestamp"
],
"properties": {
"apy": {
"type": ["number", "null"],
"type": [
"number",
"null"
],
"example": 8.45,
"description": "Annualised APY as a decimal percentage. null when insufficient data."
},
"apyStatus": {
"type": "string",
"enum": ["ok", "insufficient_data"],
"enum": [
"ok",
"insufficient_data"
],
"example": "ok"
},
"timestamp": {
Expand All @@ -546,6 +646,95 @@
}
}
},
"/api/v1/vaults": {
"get": {
"tags": [
"Vault"
],
"summary": "List vaults",
"description": "Paginated listing of active (non-deleted) vaults. `limit` is capped at 50 and `page` at 1000; exceeding `limit` fails fast with `400` / `LIMIT_EXCEEDED` instead of silently clamping, so a client that asks for an oversized page is never handed a response that looks complete. Rate limited: global 100 req / 15 min per IP; authenticated APIs 30 req / min per key. `429` responses follow the standard error envelope with a `retryAfter` detail.",
"parameters": [
{
"$ref": "#/components/parameters/pageSize"
},
{
"$ref": "#/components/parameters/pageNumber"
},
{
"$ref": "#/components/parameters/correlationId"
}
],
"responses": {
"200": {
"description": "One page of vaults",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VaultListResponse"
},
"example": {
"data": [
{
"id": "0f1b3a2c-6d4e-4a1b-9f0e-2c5d7e8b9a01",
"aum": 1250000,
"tvlUsd": "1250000.00",
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-02T00:00:00.000Z"
}
],
"pagination": {
"count": 1,
"limit": 20,
"total": 1,
"nextCursor": null,
"prevCursor": null,
"currentPage": 1,
"totalPages": 1,
"hasNextPage": false,
"hasPrevPage": false
},
"timestamp": "2026-01-01T00:00:00.000Z"
}
}
}
},
"400": {
"description": "`limit` above the published ceiling",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
},
"example": {
"error": "Bad Request",
"status": 400,
"code": "LIMIT_EXCEEDED",
"message": "limit must not exceed 50 (received 100000).",
"retryable": false,
"details": {
"field": "limit",
"requested": 100000,
"maxLimit": 50,
"defaultLimit": 20,
"maxPage": 1000
}
}
}
}
},
"429": {
"description": "Rate limited",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorEnvelope"
}
}
}
}
}
}
},
"/api/v1/vault/deposits": {
"post": {
"tags": [
Expand Down
Binary file modified backend/prisma/dev.db
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- AlterTable
ALTER TABLE "Transaction" ADD COLUMN "deletedAt" DATETIME;

-- AlterTable
ALTER TABLE "WebhookEndpoint" ADD COLUMN "tenantId" TEXT;

-- CreateTable
CREATE TABLE "SessionAuditLog" (
"id" TEXT NOT NULL PRIMARY KEY,
"walletAddress" TEXT NOT NULL,
"eventType" TEXT NOT NULL,
"reason" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"ipAddress" TEXT,
"userAgent" TEXT,
"metadata" TEXT,
"correlationId" TEXT,
"traceId" TEXT,
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- CreateIndex
CREATE INDEX "SessionAuditLog_walletAddress_timestamp_idx" ON "SessionAuditLog"("walletAddress", "timestamp" DESC);

-- CreateIndex
CREATE INDEX "SessionAuditLog_eventType_idx" ON "SessionAuditLog"("eventType");

-- CreateIndex
CREATE INDEX "SessionAuditLog_timestamp_idx" ON "SessionAuditLog"("timestamp" DESC);

-- CreateIndex
CREATE INDEX "Transaction_tenantId_idx" ON "Transaction"("tenantId");

-- CreateIndex
CREATE INDEX "Transaction_deletedAt_idx" ON "Transaction"("deletedAt");

-- CreateIndex
CREATE INDEX "WebhookEndpoint_tenantId_idx" ON "WebhookEndpoint"("tenantId");
Loading
Loading