openapi: 3.0.3
info:
title: OctoStore API
description: 'Distributed infrastructure services. That''s it.
Simple HTTP services for distributed systems with GitHub authentication.
Provides distributed locking.
'
version: 0.0.0-dev
contact:
name: OctoStore
url: https://octostore.io
license:
name: MIT
servers:
- url: https://api.octostore.io
description: Production server
- url: http://localhost:3000
description: Development server
security:
- bearerAuth: []
paths:
/auth/github:
get:
tags:
- Authentication
summary: Initiate GitHub OAuth flow
description: Redirects to GitHub OAuth authorization page
security: []
responses:
'302':
description: Redirect to GitHub OAuth
headers:
Location:
schema:
type: string
example: https://github.com/login/oauth/authorize?client_id=...
/auth/github/callback:
get:
tags:
- Authentication
summary: GitHub OAuth callback
description: Internal callback endpoint for GitHub OAuth flow
security: []
parameters:
- name: code
in: query
required: true
schema:
type: string
description: OAuth authorization code from GitHub
- name: state
in: query
required: false
schema:
type: string
description: OAuth state parameter
responses:
'200':
description: Authentication successful
content:
application/json:
schema:
type: object
properties:
token:
type: string
description: Bearer token for API access
user:
type: object
properties:
login:
type: string
description: GitHub username
avatar_url:
type: string
description: GitHub avatar URL
required:
- token
- user
'401':
$ref: '#/components/responses/Unauthorized'
/auth/token/rotate:
post:
tags:
- Authentication
summary: Rotate bearer token
description: Generate a new bearer token, invalidating the current one
responses:
'200':
description: Token rotated successfully
content:
application/json:
schema:
type: object
properties:
token:
type: string
description: New bearer token
required:
- token
'401':
$ref: '#/components/responses/Unauthorized'
/locks/{name}/acquire:
post:
tags:
- Locks
summary: Acquire a lock
description: 'Attempt to acquire a distributed lock with the given name.
Returns the lock status and lease information if successful.
'
parameters:
- $ref: '#/components/parameters/LockName'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
ttl_seconds:
type: integer
minimum: 1
maximum: 3600
description: Lock time-to-live in seconds (max 1 hour)
example: 60
required:
- ttl_seconds
responses:
'200':
description: Lock acquired or already held by caller
content:
application/json:
schema:
$ref: '#/components/schemas/LockResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'409':
description: Lock currently held by another user
content:
application/json:
schema:
$ref: '#/components/schemas/LockConflict'
'422':
$ref: '#/components/responses/ValidationError'
/locks/{name}/release:
post:
tags:
- Locks
summary: Release a lock
description: Release a previously acquired lock using its lease ID
parameters:
- $ref: '#/components/parameters/LockName'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
lease_id:
type: string
format: uuid
description: Lease ID returned when lock was acquired
example: 550e8400-e29b-41d4-a716-446655440000
required:
- lease_id
responses:
'200':
description: Lock released successfully
content:
application/json:
schema:
type: object
properties:
status:
type: string
enum:
- released
message:
type: string
example: Lock released
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/LockNotFound'
'422':
$ref: '#/components/responses/ValidationError'
/locks/{name}/renew:
post:
tags:
- Locks
summary: Renew a lock's TTL
description: Extend the time-to-live of an existing lock
parameters:
- $ref: '#/components/parameters/LockName'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
lease_id:
type: string
format: uuid
description: Lease ID of the lock to renew
example: 550e8400-e29b-41d4-a716-446655440000
ttl_seconds:
type: integer
minimum: 1
maximum: 3600
description: New TTL in seconds (max 1 hour)
example: 300
required:
- lease_id
- ttl_seconds
responses:
'200':
description: Lock renewed successfully
content:
application/json:
schema:
$ref: '#/components/schemas/LockResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/LockNotFound'
'422':
$ref: '#/components/responses/ValidationError'
/locks/{name}:
get:
tags:
- Locks
summary: Get lock status
description: Check the current status of a specific lock
parameters:
- $ref: '#/components/parameters/LockName'
responses:
'200':
description: Lock status retrieved
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/LockResponse'
- $ref: '#/components/schemas/LockNotHeld'
'401':
$ref: '#/components/responses/Unauthorized'
/locks:
get:
tags:
- Locks
summary: List locks by prefix
description: |
List all currently-held locks, optionally filtered by a hierarchical
name prefix. Lock names use `/` as the path separator
(e.g. `service/component/resource`).
parameters:
- name: prefix
in: query
required: false
schema:
type: string
description: 'Optional prefix to filter locks (e.g. "service/" or "db/primary")'
example: service/
responses:
'200':
description: List of matching locks
content:
application/json:
schema:
$ref: '#/components/schemas/ListLocksResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/sessions:
post:
tags:
- Sessions
summary: Create a new session
description: 'Create a session with a keepalive TTL. Locks acquired with this
session_id will be automatically released when the session expires.
'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
ttl_seconds:
type: integer
minimum: 10
maximum: 300
description: Session TTL in seconds (default 60, min 10, max 300)
example: 60
responses:
'201':
description: Session created
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSessionResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/sessions/{id}/keepalive:
post:
tags:
- Sessions
summary: Extend session expiry
description: Send a keepalive to extend the session TTL by its original duration
parameters:
- $ref: '#/components/parameters/SessionId'
responses:
'200':
description: Session extended
content:
application/json:
schema:
$ref: '#/components/schemas/KeepAliveResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
description: Session not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'410':
description: Session expired
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/sessions/{id}:
get:
tags:
- Sessions
summary: Get session status
description: Returns session details including the number of locks held
parameters:
- $ref: '#/components/parameters/SessionId'
responses:
'200':
description: Session status
content:
application/json:
schema:
$ref: '#/components/schemas/SessionStatusResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
description: Session not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
tags:
- Sessions
summary: Terminate a session
description: Explicitly close a session and release all associated locks
parameters:
- $ref: '#/components/parameters/SessionId'
responses:
'204':
description: Session terminated
'401':
$ref: '#/components/responses/Unauthorized'
'404':
description: Session not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/webhooks:
post:
tags:
- Webhooks
summary: Create a webhook
description: |
Register a webhook to receive POST callbacks on lock events.
URL must use HTTPS. Maximum 10 webhooks per user.
If a secret is provided, payloads are signed with HMAC-SHA256
and the signature is sent in the X-OctoStore-Signature header.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateWebhookRequest'
responses:
'201':
description: Webhook created
content:
application/json:
schema:
$ref: '#/components/schemas/WebhookResponse'
'400':
description: Invalid input (non-HTTPS URL or limit exceeded)
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
get:
tags:
- Webhooks
summary: List your webhooks
description: Returns all webhooks registered by the authenticated user.
Secrets are redacted in the response.
responses:
'200':
description: List of webhooks
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/WebhookResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/webhooks/{id}:
delete:
tags:
- Webhooks
summary: Delete a webhook
description: Remove a previously registered webhook
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: Webhook UUID
example: 550e8400-e29b-41d4-a716-446655440000
responses:
'204':
description: Webhook deleted
'401':
$ref: '#/components/responses/Unauthorized'
'404':
description: Webhook not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/health:
get:
tags:
- System
summary: Health check
description: Simple health check endpoint
security: []
responses:
'200':
description: Service is healthy
content:
text/plain:
schema:
type: string
example: OK
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: Bearer token obtained through GitHub OAuth
parameters:
SessionId:
name: id
in: path
required: true
schema:
type: string
format: uuid
description: Session UUID
example: 550e8400-e29b-41d4-a716-446655440000
LockName:
name: name
in: path
required: true
schema:
type: string
pattern: ^[a-zA-Z0-9._-]+(/[a-zA-Z0-9._-]+)*$
minLength: 1
maxLength: 256
description: 'Hierarchical lock name using `/` as path separator. Components
may contain alphanumeric characters, hyphens, underscores, and dots (max 64
chars each).'
example: service/component/my-lock
schemas:
LockResponse:
type: object
properties:
status:
type: string
enum:
- acquired
- held
description: Lock status
name:
type: string
description: Lock name
lease_id:
type: string
format: uuid
description: Unique lease identifier
fencing_token:
type: integer
description: Monotonically increasing fencing token for safety
expires_at:
type: string
format: date-time
description: Lock expiration time in RFC3339 format
holder:
type: string
description: GitHub username of the lock holder
required:
- status
- name
- lease_id
- fencing_token
- expires_at
- holder
example:
status: acquired
name: my-resource
lease_id: 550e8400-e29b-41d4-a716-446655440000
fencing_token: 42
expires_at: '2024-01-01T12:00:00Z'
holder: username
LockConflict:
type: object
properties:
error:
type: string
example: Lock already held
lock:
type: object
properties:
name:
type: string
holder:
type: string
description: Current holder's GitHub username
expires_at:
type: string
format: date-time
required:
- error
- lock
LockNotHeld:
type: object
properties:
status:
type: string
enum:
- available
name:
type: string
message:
type: string
example: Lock not currently held
required:
- status
- name
LockInfo:
type: object
properties:
name:
type: string
description: Lock name
lease_id:
type: string
format: uuid
description: Lease identifier
fencing_token:
type: integer
description: Current fencing token
expires_at:
type: string
format: date-time
description: Lock expiration time
required:
- name
- lease_id
- fencing_token
- expires_at
ListLocksResponse:
type: object
properties:
locks:
type: array
items:
type: object
properties:
name:
type: string
description: Lock name
status:
type: string
enum:
- held
description: Lock status (always "held" for active locks)
holder_id:
type: string
format: uuid
description: UUID of the lock holder
fencing_token:
type: integer
description: Monotonically increasing fencing token
expires_at:
type: string
format: date-time
description: Lock expiration time
metadata:
type: string
nullable: true
description: User-defined metadata
required:
- name
- status
- fencing_token
total:
type: integer
description: Total number of locks returned
example: 5
prefix:
type: string
nullable: true
description: The prefix filter that was applied (null if none)
example: service/
required:
- locks
- total
CreateSessionResponse:
type: object
properties:
session_id:
type: string
format: uuid
description: Unique session identifier
expires_at:
type: string
format: date-time
description: Session expiration time
keepalive_interval_secs:
type: integer
description: Recommended keepalive interval (ttl / 2)
required:
- session_id
- expires_at
- keepalive_interval_secs
KeepAliveResponse:
type: object
properties:
session_id:
type: string
format: uuid
expires_at:
type: string
format: date-time
required:
- session_id
- expires_at
SessionStatusResponse:
type: object
properties:
session_id:
type: string
format: uuid
user_id:
type: string
format: uuid
expires_at:
type: string
format: date-time
lock_count:
type: integer
description: Number of locks held by this session
active:
type: boolean
description: Whether the session is still active
required:
- session_id
- user_id
- expires_at
- lock_count
- active
CreateWebhookRequest:
type: object
properties:
url:
type: string
format: uri
description: HTTPS URL to POST event payloads to
example: https://example.com/hooks/octostore
secret:
type: string
description: Optional HMAC-SHA256 signing secret
example: my-secret-key
events:
type: array
items:
type: string
enum:
- acquired
- released
- expired
- renewed
- '*'
description: 'Event types to subscribe to (default: ["*"])'
example:
- acquired
- released
lock_pattern:
type: string
description: 'Glob pattern to filter locks (e.g. "service/*"). Omit for
all locks.'
example: service/*
required:
- url
WebhookResponse:
type: object
properties:
id:
type: string
format: uuid
description: Webhook identifier
user_id:
type: string
format: uuid
description: Owner user ID
url:
type: string
format: uri
description: Callback URL
secret:
type: string
nullable: true
description: Redacted secret (shown as "****" if set)
events:
type: array
items:
type: string
description: Subscribed event types
lock_pattern:
type: string
nullable: true
description: Lock name glob pattern
created_at:
type: string
format: date-time
description: Creation timestamp
active:
type: boolean
description: Whether the webhook is active
required:
- id
- user_id
- url
- events
- created_at
- active
WebhookPayload:
type: object
description: Payload POSTed to webhook URLs on lock events
properties:
event:
type: string
enum:
- acquired
- released
- expired
- renewed
description: Lock event type
lock:
type: string
description: Lock name
holder_id:
type: string
format: uuid
nullable: true
description: UUID of the lock holder (null for release/expiry)
fencing_token:
type: integer
nullable: true
description: Fencing token (null for release/expiry)
timestamp:
type: string
format: date-time
description: Event timestamp in RFC3339 format
required:
- event
- lock
- timestamp
Error:
type: object
properties:
error:
type: string
description: Error message
details:
type: string
description: Additional error details (optional)
required:
- error
responses:
Unauthorized:
description: Invalid or missing authentication token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error: Invalid or missing authorization header
LockNotFound:
description: Lock not found or not owned by caller
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
error: Lock not found or not owned by caller
ValidationError:
description: Request validation failed
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
invalid_ttl:
summary: Invalid TTL
value:
error: TTL must be between 1 and 3600 seconds
invalid_uuid:
summary: Invalid lease ID
value:
error: Invalid lease_id format
tags:
- name: Authentication
description: GitHub OAuth and token management
- name: Sessions
description: Session management with keepalive
- name: Locks
description: Distributed lock operations
- name: Webhooks
description: Webhook registration and management for lock event callbacks
- name: System
description: System health and status