433 lines
13 KiB
YAML
433 lines
13 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: Insforge Secrets API
|
|
version: 1.0.0
|
|
description: Secure secrets management for storing API keys, tokens, and sensitive configuration
|
|
|
|
paths:
|
|
/api/secrets:
|
|
get:
|
|
summary: List all secrets
|
|
description: Returns metadata for all secrets (without values)
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- bearerAuth: []
|
|
responses:
|
|
'200':
|
|
description: List of secret metadata
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
secrets:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/SecretMetadata'
|
|
example:
|
|
secrets:
|
|
- id: "123e4567-e89b-12d3-a456-426614174000"
|
|
key: "STRIPE_API_KEY"
|
|
isActive: true
|
|
isReserved: false
|
|
createdAt: "2024-01-21T10:30:00Z"
|
|
updatedAt: "2024-01-21T10:30:00Z"
|
|
expiresAt: null
|
|
- id: "223e4567-e89b-12d3-a456-426614174001"
|
|
key: "OPENAI_API_KEY"
|
|
isActive: true
|
|
isReserved: true
|
|
createdAt: "2024-01-20T09:15:00Z"
|
|
updatedAt: "2024-01-20T09:15:00Z"
|
|
expiresAt: "2025-01-20T09:15:00Z"
|
|
'401':
|
|
description: Unauthorized
|
|
'403':
|
|
description: Forbidden - Admin only
|
|
|
|
post:
|
|
summary: Create a new secret
|
|
description: Create a new encrypted secret with a unique key
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- bearerAuth: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- key
|
|
- value
|
|
properties:
|
|
key:
|
|
type: string
|
|
pattern: '^[A-Z0-9_]+$'
|
|
description: Unique key identifier (uppercase letters, numbers, underscores only)
|
|
example: "STRIPE_API_KEY"
|
|
value:
|
|
type: string
|
|
description: Secret value to be encrypted
|
|
example: "sk_live_..."
|
|
isReserved:
|
|
type: boolean
|
|
default: false
|
|
description: Whether the secret is protected from deletion
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Optional expiration date for the secret
|
|
responses:
|
|
'201':
|
|
description: Secret created successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
message:
|
|
type: string
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
example:
|
|
success: true
|
|
message: "Secret STRIPE_API_KEY has been created successfully"
|
|
id: "123e4567-e89b-12d3-a456-426614174000"
|
|
'400':
|
|
description: Invalid input
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
examples:
|
|
missingFields:
|
|
value:
|
|
error: "INVALID_INPUT"
|
|
message: "Both key and value are required"
|
|
statusCode: 400
|
|
invalidKeyFormat:
|
|
value:
|
|
error: "INVALID_INPUT"
|
|
message: "Invalid key format. Use uppercase letters, numbers, and underscores only (e.g., STRIPE_API_KEY)"
|
|
statusCode: 400
|
|
'409':
|
|
description: Secret already exists
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "INVALID_INPUT"
|
|
message: "Secret already exists: STRIPE_API_KEY"
|
|
statusCode: 409
|
|
|
|
/api/secrets/{key}:
|
|
get:
|
|
summary: Get secret value
|
|
description: Retrieve the decrypted value of a specific secret by its key
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- name: key
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[A-Z0-9_]+$'
|
|
description: Secret key identifier
|
|
example: "STRIPE_API_KEY"
|
|
responses:
|
|
'200':
|
|
description: Secret value retrieved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
key:
|
|
type: string
|
|
value:
|
|
type: string
|
|
example:
|
|
key: "STRIPE_API_KEY"
|
|
value: "sk_live_..."
|
|
'404':
|
|
description: Secret not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "NOT_FOUND"
|
|
message: "Secret not found: INVALID_KEY"
|
|
statusCode: 404
|
|
'401':
|
|
description: Unauthorized
|
|
'403':
|
|
description: Forbidden - Admin only
|
|
|
|
put:
|
|
summary: Update secret
|
|
description: Update an existing secret's value or metadata
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- name: key
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[A-Z0-9_]+$'
|
|
description: Secret key identifier
|
|
example: "STRIPE_API_KEY"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
value:
|
|
type: string
|
|
description: New secret value (will be encrypted)
|
|
isActive:
|
|
type: boolean
|
|
description: Whether the secret is active
|
|
isReserved:
|
|
type: boolean
|
|
description: Whether the secret is protected from deletion
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Expiration date (null to remove expiration)
|
|
responses:
|
|
'200':
|
|
description: Secret updated successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
message:
|
|
type: string
|
|
example:
|
|
success: true
|
|
message: "Secret STRIPE_API_KEY has been updated successfully"
|
|
'404':
|
|
description: Secret not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "NOT_FOUND"
|
|
message: "Secret not found: INVALID_KEY"
|
|
statusCode: 404
|
|
'500':
|
|
description: Failed to update secret
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "INTERNAL_ERROR"
|
|
message: "Failed to update secret: STRIPE_API_KEY"
|
|
statusCode: 500
|
|
|
|
delete:
|
|
summary: Delete secret
|
|
description: Mark a secret as inactive (soft delete). Cannot delete reserved secrets.
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- bearerAuth: []
|
|
parameters:
|
|
- name: key
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
pattern: '^[A-Z0-9_]+$'
|
|
description: Secret key identifier
|
|
example: "STRIPE_API_KEY"
|
|
responses:
|
|
'200':
|
|
description: Secret deleted successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
message:
|
|
type: string
|
|
example:
|
|
success: true
|
|
message: "Secret STRIPE_API_KEY has been deleted successfully"
|
|
'403':
|
|
description: Cannot delete reserved secret
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "FORBIDDEN"
|
|
message: "Cannot delete reserved secret: OPENAI_API_KEY"
|
|
statusCode: 403
|
|
'404':
|
|
description: Secret not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
example:
|
|
error: "NOT_FOUND"
|
|
message: "Secret not found: INVALID_KEY"
|
|
statusCode: 404
|
|
|
|
/api/secrets/anon-key/rotate:
|
|
post:
|
|
summary: Rotate anon key
|
|
description: >-
|
|
Rotate the project's opaque anon key (`anon_...`). A new key is
|
|
generated and returned; the old key stays valid for the grace period
|
|
(default 168 hours / 7 days, max 720) so already-deployed frontends
|
|
and mobile binaries keep working while the new key ships. Admin only.
|
|
tags:
|
|
- Admin
|
|
security:
|
|
- bearerAuth: []
|
|
requestBody:
|
|
required: false
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
gracePeriodHours:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 720
|
|
default: 168
|
|
description: How long the old key remains valid after rotation
|
|
responses:
|
|
'200':
|
|
description: Anon key rotated successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
success:
|
|
type: boolean
|
|
message:
|
|
type: string
|
|
anonKey:
|
|
type: string
|
|
description: The new anon key
|
|
oldKeyExpiresAt:
|
|
type: string
|
|
format: date-time
|
|
description: When the previous key stops being accepted
|
|
example:
|
|
success: true
|
|
message: "Anon key rotated successfully. Old key will remain valid during grace period."
|
|
anonKey: "anon_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd"
|
|
oldKeyExpiresAt: "2026-06-18T00:00:00.000Z"
|
|
'400':
|
|
description: Invalid grace period
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
'403':
|
|
description: Forbidden - admin access required
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ErrorResponse'
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
|
|
schemas:
|
|
SecretMetadata:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
format: uuid
|
|
description: Unique identifier for the secret
|
|
key:
|
|
type: string
|
|
description: Unique key identifier (uppercase with underscores)
|
|
example: "STRIPE_API_KEY"
|
|
isActive:
|
|
type: boolean
|
|
description: Whether the secret is currently active
|
|
isReserved:
|
|
type: boolean
|
|
description: Whether the secret is protected from deletion
|
|
createdAt:
|
|
type: string
|
|
format: date-time
|
|
description: When the secret was created
|
|
updatedAt:
|
|
type: string
|
|
format: date-time
|
|
description: When the secret was last updated
|
|
expiresAt:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: When the secret expires (null if no expiration)
|
|
required:
|
|
- id
|
|
- key
|
|
- isActive
|
|
- isReserved
|
|
- createdAt
|
|
- updatedAt
|
|
|
|
ErrorResponse:
|
|
type: object
|
|
required:
|
|
- error
|
|
- message
|
|
- statusCode
|
|
properties:
|
|
error:
|
|
type: string
|
|
description: Error code for programmatic handling
|
|
example: "INVALID_INPUT"
|
|
message:
|
|
type: string
|
|
description: Human-readable error message
|
|
example: "Invalid input data"
|
|
statusCode:
|
|
type: integer
|
|
description: HTTP status code
|
|
example: 400
|
|
nextActions:
|
|
type: string
|
|
description: Suggested action to resolve the error
|
|
example: "Check the request body format" |