openapi: 3.2.0 info: title: Voltair API Keys API version: 0.1.0 description: 'Infrastructure inspection platform API. All endpoints are scoped to the authenticated organization via Bearer JWT or API key. All timestamp fields on this API (createdAt, updatedAt, scheduledFor, capturedAt, expiresAt, deletedAt, etc.) are Unix timestamps in milliseconds since the epoch (UTC). Both request and response bodies use this representation.' servers: - url: / security: - BearerAuth: [] - ApiKeyAuth: [] tags: - name: API Keys paths: /api-keys: get: tags: - API Keys operationId: listApiKeys summary: List API keys description: Lists all API keys for this org. Returns metadata only (prefix, name, dates) — never the raw key or hash. parameters: - $ref: '#/components/parameters/LimitParam' - $ref: '#/components/parameters/CursorParam' responses: '200': description: Success headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: type: object required: - data - meta properties: data: type: array items: $ref: '#/components/schemas/ApiKey' meta: $ref: '#/components/schemas/PaginationMeta' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' post: tags: - API Keys operationId: createApiKey summary: Create API key description: Creates a new API key and its actor record. The role must not have broader permissions than the creating user's role (prevents privilege escalation). Response includes the raw key exactly once. parameters: - $ref: '#/components/parameters/IdempotencyKeyHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateApiKeyRequest' responses: '201': description: Created — the raw key is included and cannot be retrieved again headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: type: object required: - data - transactionId properties: data: $ref: '#/components/schemas/ApiKeyWithRawKey' transactionId: type: string format: uuid '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' /api-keys/{keyId}: parameters: - name: keyId in: path required: true schema: type: string format: uuid delete: tags: - API Keys operationId: deleteApiKey summary: Delete API key description: Soft-deletes an API key. The key is immediately unusable for authentication. responses: '200': description: Success headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: type: object required: - data - transactionId properties: data: $ref: '#/components/schemas/ApiKey' transactionId: type: string format: uuid '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '500': $ref: '#/components/responses/InternalError' components: headers: XRequestId: description: Unique request identifier (UUID) schema: type: string format: uuid responses: Conflict: description: Conflict (duplicate resource, in-use resource, or undo conflict) headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' NotFound: description: Resource not found headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' InternalError: description: Internal server error headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Forbidden: description: Insufficient permissions headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Unauthorized: description: Authentication required headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' BadRequest: description: Bad request or validation error headers: X-Request-Id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' TooManyRequests: description: Rate limit exceeded headers: X-Request-Id: $ref: '#/components/headers/XRequestId' X-RateLimit-Limit: schema: type: integer description: Maximum requests per window X-RateLimit-Remaining: schema: type: integer description: Requests remaining in current window X-RateLimit-Reset: schema: type: number description: Unix timestamp (ms) when the window resets Retry-After: schema: type: integer description: Seconds until the next rate limit window content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' schemas: ApiKeyWithRawKey: description: API key returned on creation, including the raw key shown only once allOf: - $ref: '#/components/schemas/ApiKey' - type: object required: - rawKey properties: rawKey: type: string description: The raw API key; returned only on creation and cannot be retrieved again ErrorResponse: type: object required: - error properties: error: type: object required: - code - message properties: code: type: string description: Machine-readable error code message: type: string description: Human-readable description details: type: object additionalProperties: true description: Optional structured info (e.g. field-level validation errors, conflictingEventIds) ApiKey: type: object required: - id - actorId - name - prefix - createdBy - deletedAt - createdAt - lastUsedAt - expiresAt properties: id: type: string format: uuid actorId: type: string format: uuid name: type: string prefix: type: string description: First 12 characters, safe to display (e.g. "sk_live_a1b2") createdBy: type: string format: uuid description: User ID of the creator deletedAt: type: - number - 'null' createdAt: type: number lastUsedAt: type: - number - 'null' expiresAt: type: - number - 'null' PaginationMeta: type: object required: - cursor properties: cursor: type: - string - 'null' description: Opaque cursor for the next page; null when no more results total: type: integer description: Total matching results across all pages; included when cheaply computable CreateApiKeyRequest: type: object required: - name - roleId properties: name: type: string roleId: type: string format: uuid expiresAt: type: number description: Unix timestamp in milliseconds for key expiration parameters: LimitParam: name: limit in: query schema: type: integer minimum: 1 maximum: 200 description: Page size (default 50, max 200) IdempotencyKeyHeader: name: Idempotency-Key in: header required: false schema: type: string format: uuid description: Idempotency key for POST requests. If a transaction with the same key already exists for the org, the server returns the original response without re-executing. Keys are valid for 48 hours. CursorParam: name: cursor in: query schema: type: string description: Opaque pagination cursor from a previous response securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT description: Cognito JWT access token ApiKeyAuth: type: apiKey in: header name: X-API-Key description: Organization-scoped API key