openapi: 3.1.0 info: title: Euler Data API (V3) Accounts Auth API version: 3.0.0 description: "API specification for Euler Data v3. This spec defines the current\nresource-oriented contract for the platform, with emphasis on consistent\nschemas, predictable REST semantics, caching, observability, and a uniform\ndeveloper experience.\n\nMigration notes\n- Euler Data v3 is not a path-for-path clone of v1/v2. Some endpoints map\n directly, some require multiple v3 calls, and some legacy aggregates were\n intentionally removed.\n- The live [migration guide](/v3/docs/migration) explains how legacy v1/v2\n endpoints and payloads map into the v3 resource model.\n- Important semantic changes:\n - Vault base APY and rewards APY are exposed separately in v3.\n - Account positions are flattened into account-vault rows instead of the\n legacy nested sub-account shape.\n - All supported API paths live under `/v3`; legacy paths return `410`.\n - Summary endpoints prefer smaller, focused resources over large\n BFF-style payloads.\n\nBase path: /v3\n\nConventions\n- JSON field naming: camelCase\n- Addresses: accept lowercase or checksum; responses are EIP-55 checksummed\n- Public APY values (`*Apy`, `apy*`): percent numbers with 6 decimal places (e.g., 5.123456 = 5.123456%)\n- Contract interest-rate fields (`borrowAPY`, `supplyAPY` in `interestRates`): decimal strings in contract fraction units\n- Ratios (utilization, LTV, etc.): numbers with 6 decimal places\n- Response timestamps: ISO-8601 strings (UTC). Some legacy fields still expose unix\n seconds for compatibility and include `*Iso` companions.\n- Query time ranges: unix seconds (from/to)\n\nUnit policy\n- v3 separates market metadata from contract verdicts by both name and type.\n- Market-normalized USD prices and USD values are JSON numbers. These are values\n derived from off-chain pricing, cached price snapshots, or API-level market math.\n This includes `/v3/prices`, token price routes, vault/earn/protocol USD totals,\n portfolio market values, portfolio USD liquidation/risk fields such as\n `liabilityValueUsd`, `totalCollateralValueUsd`, `borrowLiquidationPriceUsd`,\n and `collateralLiquidationPricesUsd`, and vault holder `assetsUsd`.\n- Contract-derived oracle and raw account-liquidity verdict values are exact strings.\n These preserve the integer value used by contracts, normalized to 18 decimals\n for API exposure. Oracle prices are quoted in the vault unit of account, which\n can be USD, ETH, or another asset. This includes `/v3/oracles/prices`, raw\n account-liquidity `liabilityValue` and `totalCollateralValue` objects,\n `liabilityValueBorrowing`, `liabilityValueLiquidation`, and collateral verdict\n values.\n- On-chain token quantities are bigint strings in their native unit scale. This\n includes amounts, balances, shares, assets, borrows, reward amounts, liquidation\n repay/yield amounts, and block numbers.\n- Caps are also bigint strings, but EVK caps are exposed as resolved underlying\n asset-unit amounts rather than packed contract config words.\n- LTV config values use canonical basis-point strings. Legacy decimal aliases\n may remain on config-history payloads for compatibility when explicitly named.\n\nRate limiting\n- Optional API keys for higher limits\n- Default limits are configurable; current defaults: free tier 100 req/min (IP-based)\n and authenticated 1000 req/min (per API key), 60s window.\n- By default, proxy headers are not trusted for IP identity unless explicitly enabled.\n- Redis failures default to fail-closed (503) for strict enforcement; fail-open is opt-in.\n- RateLimit-* (IETF draft-6) and legacy X-RateLimit-* headers are returned.\n\nCORS\n- Public API responses use wildcard CORS, including preflight responses for unsafe methods.\n- Unsafe methods with request bodies must use a JSON media type.\n\nCaching\n- Cache-Control headers are returned for cacheable endpoints.\n- Defaults (may be adjusted in config):\n - Prices: 300s\n - APYs: 600s\n - Rewards: 900s\n - General responses: 60s (short 30s, long 300s)\n\nErrors\n- All errors use a standard envelope: { error: { code, message, requestId, details? } }\n- Domain error codes are documented in this spec.\n- JSON request bodies must use `Content-Type: application/json` or another `application/*+json` media type.\n- Request bodies larger than 1 MiB are rejected before route parsing with `413 PAYLOAD_TOO_LARGE`.\n" servers: - url: / description: Current environment security: [] tags: - name: Auth description: Authentication and authorization conventions used across the API, including API keys, admin secrets, and rate-limit behavior. paths: /v3/auth/login: get: tags: - Auth summary: Start platform-operator Google SSO description: Redirects the browser to Google OAuth/OIDC and sets a signed OAuth state cookie. x-status: implemented responses: '302': description: Redirect to configured Google OAuth authorization URL. headers: Location: description: Google authorization URL. schema: type: string format: uri Set-Cookie: description: Signed OAuth state cookie with httpOnly, SameSite, path, and expiry. schema: type: string X-Request-Id: $ref: '#/components/headers/X-Request-Id' operationId: startPlatformAuthLogin /v3/auth/callback: get: tags: - Auth summary: Complete platform-operator Google SSO callback description: Validates state, nonce, Google ID-token claims, and active platform-admin access. x-status: implemented parameters: - name: code in: query required: true schema: type: string description: OAuth authorization code. Never returned in responses. - name: state in: query required: true schema: type: string description: OAuth anti-forgery state bound to the signed state cookie. responses: '302': description: Successful callback; session and CSRF cookies are set, then browser is redirected. headers: Location: description: Configured post-login redirect URL. schema: type: string format: uri Set-Cookie: description: Signed httpOnly session cookie, readable CSRF cookie, and cleared OAuth state cookie. schema: type: string X-Request-Id: $ref: '#/components/headers/X-Request-Id' '400': description: Invalid callback, state, nonce, or ID token. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Google identity is not an active platform admin. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' operationId: completePlatformAuthCallback /v3/auth/session: get: tags: - Auth summary: Get current platform-operator session x-status: implemented security: - PlatformSession: [] responses: '200': description: Active platform-admin session identity. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object required: - data - meta properties: data: $ref: '#/components/schemas/PlatformAuthSession' meta: $ref: '#/components/schemas/PaginationMeta' '401': description: Missing, invalid, or expired platform-admin session. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Authenticated session exists, but platform-admin access is not active. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' operationId: getPlatformAuthSession /v3/auth/logout: post: tags: - Auth summary: End current platform-operator session x-status: implemented security: - PlatformSession: [] PlatformCsrf: [] responses: '200': description: Session cookies were cleared. headers: Set-Cookie: description: Expired session and CSRF cookies. schema: type: string X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object required: - data - meta properties: data: type: object required: - loggedOut properties: loggedOut: type: boolean meta: $ref: '#/components/schemas/PaginationMeta' '401': description: Invalid signed platform-admin session. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Missing or mismatched CSRF token, or inactive platform-admin access. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' operationId: logoutPlatformAuthSession /v3/platform-admins: post: tags: - Auth summary: Grant platform-admin access description: 'Creates an active platform-admin access row for a Google email address. Requires a valid platform session and CSRF token because this is an unsafe cookie-authenticated write. ' x-status: implemented security: - PlatformSession: [] PlatformCsrf: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PlatformAdminAccessRequest' responses: '200': description: Existing revoked platform-admin access was reactivated. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object required: - data - meta properties: data: $ref: '#/components/schemas/PlatformAdminCreateAccessResult' meta: $ref: '#/components/schemas/PaginationMeta' '201': description: Platform-admin access was created. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object required: - data - meta properties: data: $ref: '#/components/schemas/PlatformAdminCreateAccessResult' meta: $ref: '#/components/schemas/PaginationMeta' '400': description: Malformed JSON request body. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing, invalid, or expired platform-admin session. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Inactive platform-admin access, missing CSRF token, or mismatched CSRF token. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: Platform-admin access already exists. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '422': description: Missing or invalid Google email. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' operationId: createPlatformAdminAccess /v3/platform-admins/revoke: post: tags: - Auth summary: Revoke platform-admin access description: 'Soft-revokes platform-admin access for a Google email address. Requires a valid platform session and CSRF token because this is an unsafe cookie-authenticated write. ' x-status: implemented security: - PlatformSession: [] PlatformCsrf: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PlatformAdminAccessRequest' responses: '200': description: Platform-admin access was revoked. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object required: - data - meta properties: data: $ref: '#/components/schemas/PlatformAdminRevokeAccessResult' meta: $ref: '#/components/schemas/PaginationMeta' '400': description: Malformed JSON request body. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Missing, invalid, or expired platform-admin session. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Inactive platform-admin access, missing CSRF token, or mismatched CSRF token. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Platform-admin access was not found. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: Platform-admin access was already revoked. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '422': description: Missing or invalid Google email. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' operationId: revokePlatformAdminAccess /v3/toolbox/admin/api-keys: get: tags: - Auth summary: List API keys for toolbox Admin description: Lists API key metadata for the toolbox Admin screen. Requires an active platform-admin session and a Google email present in `TOOLBOX_ADMIN_EMAILS`. x-status: implemented security: - PlatformSession: [] responses: '200': description: API key metadata visible to toolbox admins. headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object required: - data - meta properties: data: type: array items: $ref: '#/components/schemas/ApiKeyResponse' meta: $ref: '#/components/schemas/PaginationMeta' '401': $ref: '#/components/responses/Error401' '403': $ref: '#/components/responses/Error403' '500': $ref: '#/components/responses/Error500' operationId: getToolboxAdminApiKeys /v3/admin/api-keys: post: tags: - Auth summary: Create an API key (admin) x-status: implemented security: - AdminSecret: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ApiKeyCreateRequest' responses: '201': description: API key created headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ApiKeyCreateResponse' '400': $ref: '#/components/responses/Error400' '401': $ref: '#/components/responses/Error401' '403': $ref: '#/components/responses/Error403' '413': $ref: '#/components/responses/Error413' '415': $ref: '#/components/responses/Error415' '500': $ref: '#/components/responses/Error500' operationId: postAdminApiKeys get: tags: - Auth summary: List API keys (admin) x-status: implemented security: - AdminSecret: [] responses: '200': description: API keys headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/ApiKeyResponse' '401': $ref: '#/components/responses/Error401' '403': $ref: '#/components/responses/Error403' '500': $ref: '#/components/responses/Error500' operationId: getAdminApiKeys /v3/admin/api-keys/{id}: get: tags: - Auth summary: Get API key by id (admin) x-status: implemented security: - AdminSecret: [] parameters: - name: id in: path required: true schema: type: string responses: '200': description: API key headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ApiKeyResponse' '401': $ref: '#/components/responses/Error401' '403': $ref: '#/components/responses/Error403' '404': $ref: '#/components/responses/Error404' '500': $ref: '#/components/responses/Error500' operationId: getAdminApiKeysById patch: tags: - Auth summary: Update API key (admin) x-status: implemented security: - AdminSecret: [] parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: type: object properties: name: type: string rateLimit: type: integer isActive: type: boolean keyType: type: string enum: - standard - curator scopeMode: type: string enum: - all_vaults - allowlist vaultAllowlist: type: array items: type: object required: - chainId - vaultAddress properties: chainId: type: integer vaultAddress: type: string responses: '200': description: Updated headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object properties: data: type: object '400': $ref: '#/components/responses/Error400' '401': $ref: '#/components/responses/Error401' '403': $ref: '#/components/responses/Error403' '404': $ref: '#/components/responses/Error404' '413': $ref: '#/components/responses/Error413' '415': $ref: '#/components/responses/Error415' '500': $ref: '#/components/responses/Error500' operationId: patchAdminApiKeysById delete: tags: - Auth summary: Revoke API key (admin) x-status: implemented security: - AdminSecret: [] parameters: - name: id in: path required: true schema: type: string responses: '200': description: Revoked headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object properties: data: type: object '401': $ref: '#/components/responses/Error401' '403': $ref: '#/components/responses/Error403' '404': $ref: '#/components/responses/Error404' '500': $ref: '#/components/responses/Error500' operationId: deleteAdminApiKeysById /v3/admin/api-keys/{id}/usage: get: tags: - Auth summary: Usage stats for an API key (admin) x-status: implemented security: - AdminSecret: [] parameters: - name: id in: path required: true schema: type: string - name: days in: query required: false schema: type: integer responses: '200': description: Usage stats headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: type: object properties: data: $ref: '#/components/schemas/ApiKeyUsageResponse' '401': $ref: '#/components/responses/Error401' '403': $ref: '#/components/responses/Error403' '404': $ref: '#/components/responses/Error404' '500': $ref: '#/components/responses/Error500' operationId: getAdminApiKeysByIdUsage components: schemas: ApiKeyUsageResponse: type: object required: - period - summary - endpoints properties: period: type: object required: - days - since properties: days: type: integer since: type: string format: date-time summary: type: object required: - totalRequests - avgResponseTimeMs - errorCount properties: totalRequests: type: integer avgResponseTimeMs: type: integer errorCount: type: integer endpoints: type: array items: type: object required: - endpoint - method - count - avgResponseTime properties: endpoint: type: string method: type: string count: type: integer avgResponseTime: type: integer PlatformAdminAccess: type: object required: - id - googleEmail - createdAt - revokedAt properties: id: type: string format: uuid googleEmail: type: string format: email createdAt: type: string format: date-time revokedAt: type: string format: date-time nullable: true ApiKeyResponse: type: object required: - id - name - keyType - rateLimit - isActive - createdAt properties: id: type: string keyPrefix: type: string name: type: string ownerEmail: type: string nullable: true keyType: type: string enum: - standard - curator scopeMode: type: string enum: - all_vaults - allowlist nullable: true vaultAllowlist: type: array items: type: object required: - chainId - vaultAddress properties: chainId: type: integer vaultAddress: type: string rateLimit: type: integer isActive: type: boolean createdAt: type: string format: date-time lastUsedAt: type: string format: date-time nullable: true expiresAt: type: string format: date-time nullable: true PlatformAdminAccessRequest: type: object additionalProperties: false required: - googleEmail properties: googleEmail: type: string format: email description: Google Workspace email address to grant or revoke. ErrorResponse: type: object required: - error properties: error: type: object required: - code - message - requestId properties: code: type: string description: 'Domain error code. Examples: - INVALID_ADDRESS - CHAIN_NOT_SUPPORTED - VAULT_NOT_FOUND - TOKEN_NOT_FOUND - ACCOUNT_NOT_FOUND - VALIDATION_ERROR - RATE_LIMIT_EXCEEDED - UNSUPPORTED_MEDIA_TYPE - UNAUTHORIZED - FORBIDDEN - INTERNAL_ERROR ' message: type: string requestId: type: string details: type: object additionalProperties: true ApiKeyCreateRequest: type: object required: - name properties: name: type: string ownerEmail: type: string format: email keyType: type: string enum: - standard - curator default: standard scopeMode: type: string enum: - all_vaults - allowlist vaultAllowlist: type: array items: type: object required: - chainId - vaultAddress properties: chainId: type: integer vaultAddress: type: string rateLimit: type: integer expiresInDays: type: integer ApiKeyCreateResponse: type: object required: - message - key - keyPrefix - id - name - keyType - rateLimit properties: message: type: string key: type: string keyPrefix: type: string id: type: string name: type: string keyType: type: string enum: - standard - curator scopeMode: type: string enum: - all_vaults - allowlist nullable: true vaultAllowlist: type: array items: type: object required: - chainId - vaultAddress properties: chainId: type: integer vaultAddress: type: string rateLimit: type: integer expiresAt: type: string format: date-time nullable: true PlatformAdminCreateAccessResult: type: object required: - admin - state properties: admin: $ref: '#/components/schemas/PlatformAdminAccess' state: type: string enum: - created - reactivated PlatformAdminRevokeAccessResult: type: object required: - admin - state properties: admin: $ref: '#/components/schemas/PlatformAdminAccess' state: type: string enum: - revoked PlatformAuthSession: type: object required: - platformAdminId - googleEmail - googleSubject - active - expiresAt properties: platformAdminId: type: string format: uuid googleEmail: type: string format: email googleSubject: type: string active: type: boolean const: true expiresAt: type: string format: date-time PaginationMeta: type: object required: - timestamp properties: total: type: integer description: Exact total count when the endpoint provides one. hasMore: type: boolean description: Indicates whether another page exists beyond the current page. offset: type: integer limit: type: integer description: Echoed page size after endpoint-side clamping. timestamp: type: string format: date-time chainId: type: string description: Comma-separated chain IDs for multi-chain responses. responses: Error413: description: Request body too large headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Error401: description: Unauthorized headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Error415: description: Unsupported media type headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Error500: description: Internal server error headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Error400: description: Bad request headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Error404: description: Not found headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Error403: description: Forbidden headers: X-Request-Id: $ref: '#/components/headers/X-Request-Id' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' headers: X-Request-Id: description: Request identifier for tracing. schema: type: string securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key description: API key authentication (optional; higher rate limits). BearerAuth: type: http scheme: bearer bearerFormat: API key description: 'Alternative to X-API-Key using Authorization: Bearer ' AdminSecret: type: apiKey in: header name: X-Admin-Secret description: Admin secret (server-to-server) for API key management. PlatformSession: type: apiKey in: cookie name: euler_platform_session description: Signed, httpOnly platform-operator browser session cookie. PlatformCsrf: type: apiKey in: header name: X-CSRF-Token description: Double-submit CSRF token required for unsafe cookie-authenticated methods.