{"openapi":"3.1.0","info":{"title":"Solenoid API","version":"1.0.0","description":"Project management, usage metering, webhook scheduling, and digital notary API for SaaS applications","contact":{"name":"Solenoid Systems","email":"robin@solenoid.systems","url":"https://solenoid.systems"}},"servers":[{"url":"https://api.solenoid.systems","description":"Production"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key obtained from signup endpoint"}},"schemas":{"Error":{"type":"object","required":["error","code"],"properties":{"error":{"type":"string","description":"Human-readable error message"},"code":{"type":"string","enum":["validation_error","insufficient_balance","rate_limit_exceeded","unauthorized","not_found","request_too_large","timeout","service_unavailable","internal_error"]},"fields":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}},"description":"Field-level validation errors, present on validation_error responses"}}},"Balance":{"type":"object","required":["balance","user_id"],"properties":{"balance":{"type":"integer","description":"Current balance in units"},"user_id":{"type":"string","description":"User ID"}}},"TenantBalance":{"type":"object","required":["balance","meter_id"],"properties":{"balance":{"type":"integer","description":"Current balance in units"},"meter_id":{"type":"string","description":"Tenant meter ID"}}},"DeductRequest":{"type":"object","required":["amount"],"properties":{"amount":{"type":"integer","minimum":1,"description":"Amount to deduct"}}},"RefillRequest":{"type":"object","required":["amount"],"properties":{"amount":{"type":"integer","minimum":0,"description":"Amount to add"}}},"ResetRequest":{"type":"object","required":["balance"],"properties":{"balance":{"type":"integer","minimum":0,"description":"New balance"}}},"SignupRequest":{"type":"object","required":["email","tier"],"properties":{"email":{"type":"string","format":"email"},"tier":{"type":"string","enum":["free","pro"]}}},"SignupResponse":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string"},"api_key":{"type":"string","description":"Only returned for free tier"}}},"RelayRequest":{"type":"object","required":["target_url","method","delay","payload","headers"],"properties":{"target_url":{"type":"string","format":"uri"},"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE"]},"delay":{"type":"string","description":"Delay string (e.g., \"30s\", \"5m\", \"2h\", \"7d\")","pattern":"^\\d+[smhd]$"},"payload":{"type":"object","additionalProperties":true},"headers":{"type":"object","additionalProperties":{"type":"string"}}}},"RelayResponse":{"type":"object","required":["relay_id","scheduled_for","status"],"properties":{"relay_id":{"type":"string"},"scheduled_for":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["scheduled"]}}},"NotarizeLogRequest":{"type":"object","required":["log"],"properties":{"log":{"type":"object","additionalProperties":true,"description":"Arbitrary JSON log object (non-empty)"}}},"StorageConfigRequest":{"type":"object","required":["endpoint","bucket","region","accessKeyId","secretAccessKey"],"properties":{"endpoint":{"type":"string","format":"uri","description":"S3-compatible endpoint URL (must be HTTPS)"},"bucket":{"type":"string","minLength":3,"maxLength":63,"description":"S3 bucket name"},"region":{"type":"string","minLength":1,"description":"AWS region (e.g., us-east-1)"},"accessKeyId":{"type":"string","minLength":16,"description":"S3 access key ID"},"secretAccessKey":{"type":"string","minLength":16,"description":"S3 secret access key"}}},"JSONReceiptV1":{"type":"object","required":["version","sequence_id","content_hash","prev_hash","chain_hash","signature","timestamp","public_key","log"],"properties":{"version":{"type":"integer","enum":[1],"description":"Receipt version (always 1)"},"sequence_id":{"type":"string","description":"BigInt sequence ID (string to support >MAX_SAFE_INTEGER)"},"content_hash":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"SHA-256 hash of JSON.stringify(log)"},"prev_hash":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"Previous chain_hash (0s for first entry)"},"chain_hash":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"SHA-256(prev_hash || sequence_id || content_hash)"},"signature":{"type":"string","pattern":"^[a-f0-9]{128}$","description":"Ed25519 signature of chain_hash"},"timestamp":{"type":"integer","description":"Unix epoch milliseconds"},"public_key":{"type":"object","description":"Ed25519 public key JWK"},"log":{"type":"object","additionalProperties":true,"description":"Original log object"}}},"VerifyReceiptRequest":{"type":"object","required":["receipt"],"properties":{"receipt":{"$ref":"#/components/schemas/JSONReceiptV1"}}},"VerificationResult":{"type":"object","required":["valid","checks","metadata"],"properties":{"valid":{"type":"boolean","description":"True if all checks passed, false otherwise"},"checks":{"type":"object","required":["signature_valid","content_hash_matches","chain_link_valid"],"properties":{"signature_valid":{"type":"boolean","description":"Ed25519 signature verification result"},"content_hash_matches":{"type":"boolean","description":"SHA-256(JSON.stringify(log)) matches content_hash"},"chain_link_valid":{"type":"boolean","description":"SHA-256(prev_hash || seq_id || content_hash) matches chain_hash"}}},"metadata":{"type":"object","required":["sequence_id","timestamp","public_key"],"properties":{"sequence_id":{"type":"string","description":"Sequence ID from receipt"},"timestamp":{"type":"integer","description":"Timestamp from receipt"},"public_key":{"type":"object","description":"Ed25519 public key JWK from receipt"}}},"errors":{"type":"array","items":{"type":"string"},"description":"List of specific failures (omitted if all checks pass)"}}},"WitnessInitResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"True if new chain created, false if already exists"},"publicKey":{"type":"object","description":"Ed25519 public key JWK (only if newly created)"},"createdAt":{"type":"integer","description":"Creation timestamp (only if newly created)"},"chain_id":{"type":"string","pattern":"^wc_[A-Za-z0-9_-]{32}$","example":"wc_9tQvXm2LpR4sK7nB1cD8fG3hJ5wY6zA0","description":"The chain public identifier. Returned whether or not this call created the chain, so init is a safe way to recover it. Publish it with your receipts: it is what GET /v1/witness/pubkey/{chainId} takes, and the only thing that addresses the chain without an API key. Derived from account and project, so it is stable and reveals neither."}}},"WitnessPublicKey":{"type":"object","description":"Ed25519 public key JWK for receipt signature verification"},"LatchAcquireRequest":{"type":"object","properties":{"ttl":{"type":"integer","minimum":1000,"maximum":900000,"description":"Lock TTL in milliseconds (default: 10000)"},"timeout":{"type":"integer","minimum":1000,"maximum":900000,"description":"Acquire timeout in milliseconds (default: 10000)"},"token":{"type":"string","format":"uuid","description":"Reentrant token (optional)"}}},"LatchAcquireResponse":{"type":"object","required":["status","token","expires_at","key","reentrant"],"properties":{"status":{"type":"string","enum":["acquired"]},"token":{"type":"string","format":"uuid","description":"Lock token for release"},"expires_at":{"type":"integer","description":"Unix epoch milliseconds"},"key":{"type":"string","description":"Lock key"},"reentrant":{"type":"boolean","description":"True if reentrant acquire"}}},"LatchReleaseRequest":{"type":"object","required":["token"],"properties":{"token":{"type":"string","format":"uuid","description":"Lock token from acquire"}}},"LatchReleaseResponse":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["released"]}}},"LatchStatusResponse":{"type":"object","required":["locked","queue_depth","ttl_remaining"],"properties":{"locked":{"type":"boolean","description":"True if lock is held"},"queue_depth":{"type":"integer","description":"Number of waiting acquire requests"},"ttl_remaining":{"type":"integer","nullable":true,"description":"Milliseconds until lock expires (null if not locked)"}}},"MonitorResponse":{"type":"object","required":["id","url","method","interval","active","created_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Monitor ID"},"url":{"type":"string","format":"uri","description":"URL to monitor"},"method":{"type":"string","enum":["GET","HEAD","POST"],"description":"HTTP method"},"interval":{"type":"integer","description":"Check interval in milliseconds"},"active":{"type":"boolean","description":"Monitor active status"},"status":{"type":"string","enum":["up","down","unknown"],"description":"Current status"},"latency_ms":{"type":"integer","description":"Last check latency in milliseconds"},"last_checked_at":{"type":"integer","description":"Unix timestamp of last check"},"created_at":{"type":"integer","description":"Unix timestamp of creation"},"warning":{"type":"string","description":"Warning message (e.g., interval coerced to tier minimum)"}}},"MonitorStatusResponse":{"type":"object","required":["monitor_id","url","status","latency_ms","last_checked_at","consecutive_failures"],"properties":{"monitor_id":{"type":"string","format":"uuid","description":"Monitor ID"},"url":{"type":"string","format":"uri","description":"Monitored URL"},"status":{"type":"string","enum":["up","down","unknown"],"description":"Current status"},"latency_ms":{"type":"integer","description":"Last check latency in milliseconds"},"last_checked_at":{"type":"integer","description":"Unix timestamp of last check"},"consecutive_failures":{"type":"integer","description":"Number of consecutive failures"}}},"CreateMonitorRequest":{"type":"object","required":["url","interval"],"properties":{"url":{"type":"string","format":"uri","description":"URL to monitor"},"method":{"type":"string","enum":["GET","HEAD","POST"],"default":"GET","description":"HTTP method"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom headers (Pro/Scale only)"},"body":{"type":"string","maxLength":10000,"description":"Request body for POST"},"timeout":{"type":"integer","minimum":1000,"maximum":30000,"default":10000,"description":"Timeout in milliseconds"},"interval":{"type":"integer","minimum":15000,"maximum":300000,"description":"Check interval in milliseconds (tier-dependent minimum)"},"consecutive_threshold":{"type":"integer","minimum":1,"maximum":10,"default":1,"description":"Consecutive failures before triggering alert"},"cooldown_ms":{"type":"integer","minimum":60000,"maximum":3600000,"default":300000,"description":"Cooldown between alerts in milliseconds"},"webhook_url":{"type":"string","format":"uri","description":"Webhook URL for notifications (Starter+ only)"},"email_alert":{"type":"boolean","default":false,"description":"Enable email alerts (Pro/Scale only)"},"notify_recovery":{"type":"boolean","default":false,"description":"Send notification when service recovers"}}},"UpdateMonitorRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","description":"URL to monitor"},"method":{"type":"string","enum":["GET","HEAD","POST"],"description":"HTTP method"},"headers":{"type":"object","additionalProperties":{"type":"string"},"nullable":true,"description":"Custom headers (Pro/Scale only)"},"body":{"type":"string","maxLength":10000,"nullable":true,"description":"Request body for POST"},"timeout":{"type":"integer","minimum":1000,"maximum":30000,"description":"Timeout in milliseconds"},"interval":{"type":"integer","minimum":15000,"maximum":300000,"description":"Check interval in milliseconds"},"consecutive_threshold":{"type":"integer","minimum":1,"maximum":10,"description":"Consecutive failures before triggering alert"},"cooldown_ms":{"type":"integer","minimum":60000,"maximum":3600000,"description":"Cooldown between alerts in milliseconds"},"webhook_url":{"type":"string","format":"uri","nullable":true,"description":"Webhook URL for notifications"},"email_alert":{"type":"boolean","description":"Enable email alerts (Pro/Scale only)"},"notify_recovery":{"type":"boolean","description":"Send notification when service recovers"},"active":{"type":"boolean","description":"Monitor active status"}}},"ListMonitorsResponse":{"type":"object","required":["monitors"],"properties":{"monitors":{"type":"array","items":{"$ref":"#/components/schemas/MonitorStatusResponse"}},"cursor":{"type":"string","description":"Pagination cursor"},"total":{"type":"integer","description":"Total count (optional)"}}},"CreateKeyRequest":{"type":"object","required":["prefix","name","scopes"],"properties":{"prefix":{"type":"string","pattern":"^[a-z0-9_]+$","maxLength":32},"name":{"type":"string","minLength":1,"maxLength":255},"scopes":{"type":"array","items":{"type":"string"},"minItems":1},"environment":{"type":"string"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"expires_at":{"type":"string","format":"date-time"}}},"CreateKeyResponse":{"type":"object","required":["id","secret","name","prefix","scopes","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"secret":{"type":"string","description":"Only shown once at creation"},"name":{"type":"string"},"prefix":{"type":"string"},"last4":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"environment":{"type":"string"},"metadata":{"type":"object","additionalProperties":{"type":"string"}},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time","nullable":true}}},"VerifyKeyRequest":{"type":"object","required":["secret"],"properties":{"secret":{"type":"string","minLength":1}}},"VerifyKeyResponse":{"type":"object","required":["valid"],"properties":{"valid":{"type":"boolean"},"key_id":{"type":"string","format":"uuid","nullable":true},"project_id":{"type":"string","nullable":true},"scopes":{"type":"array","items":{"type":"string"},"nullable":true},"error":{"type":"string","enum":["invalid_key","expired","revoked","deleted"],"nullable":true}}},"Project":{"type":"object","required":["id","name","slug","created_at","updated_at"],"properties":{"id":{"type":"string","description":"Unique project ID"},"name":{"type":"string","description":"Project name"},"slug":{"type":"string","description":"URL-safe identifier, immutable"},"description":{"type":"string","nullable":true,"description":"Project description"},"created_at":{"type":"string","format":"date-time","description":"Creation timestamp"},"updated_at":{"type":"string","format":"date-time","description":"Last update timestamp"}}},"ProjectOverview":{"type":"object","required":["project_id","project_name","resources","usage"],"properties":{"project_id":{"type":"string","description":"Project ID"},"project_name":{"type":"string","description":"Project name"},"resources":{"type":"object","required":["flags","monitors","relays","buckets","keys","locks","chains","meters"],"properties":{"flags":{"type":"integer","description":"Number of feature flags"},"monitors":{"type":"integer","description":"Number of uptime monitors"},"relays":{"type":"integer","description":"Number of scheduled webhooks"},"buckets":{"type":"integer","description":"Number of webhook buckets"},"keys":{"type":"integer","description":"Number of API keys"},"locks":{"type":"integer","description":"Number of distributed locks"},"chains":{"type":"integer","description":"Number of witness chains"},"meters":{"type":"integer","description":"Number of tenant meters"}}},"usage":{"type":"object","required":["meter_balance"],"properties":{"meter_balance":{"type":"integer","description":"Current meter balance"},"last_activity":{"type":"string","format":"date-time","nullable":true,"description":"Last activity timestamp"}}}}}}},"paths":{"/v1/meter/balance":{"get":{"summary":"Get own balance","description":"Returns the current balance for the authenticated user","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Balance retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Meter"]}},"/v1/meter/refill":{"post":{"summary":"Refill own balance","description":"Adds units to the authenticated user balance","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefillRequest"}}}},"responses":{"200":{"description":"Refill successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Balance"}}}}},"tags":["Meter"]}},"/v1/meter/{meterId}/balance":{"get":{"summary":"Get tenant meter balance","description":"Returns the current balance for a tenant meter","security":[{"bearerAuth":[]}],"parameters":[{"name":"meterId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","maxLength":64},"description":"Tenant meter ID"}],"responses":{"200":{"description":"Balance retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantBalance"}}}}},"tags":["Meter - Multi-Tenant"]}},"/v1/meter/{meterId}/deduct":{"post":{"summary":"Deduct from tenant meter","description":"Deducts units from a tenant meter balance","security":[{"bearerAuth":[]}],"parameters":[{"name":"meterId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","maxLength":64}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeductRequest"}}}},"responses":{"200":{"description":"Deduction successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantBalance"}}}},"402":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Meter - Multi-Tenant"]}},"/v1/meter/{meterId}/refill":{"post":{"summary":"Refill tenant meter","description":"Adds units to a tenant meter balance","security":[{"bearerAuth":[]}],"parameters":[{"name":"meterId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","maxLength":64}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefillRequest"}}}},"responses":{"200":{"description":"Refill successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantBalance"}}}}},"tags":["Meter - Multi-Tenant"]}},"/v1/meter/{meterId}/reset":{"post":{"summary":"Reset tenant meter","description":"Resets a tenant meter to a specific balance","security":[{"bearerAuth":[]}],"parameters":[{"name":"meterId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","maxLength":64}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetRequest"}}}},"responses":{"200":{"description":"Reset successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantBalance"}}}}},"tags":["Meter - Multi-Tenant"]}},"/v1/relay":{"post":{"summary":"Schedule webhook delivery","description":"Schedules a webhook to be delivered after a specified delay","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelayRequest"}}}},"responses":{"201":{"description":"Webhook scheduled successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RelayResponse"}}}},"402":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Relay"]}},"/v1/witness/sys/init":{"post":{"summary":"Initialize witness chain","description":"Creates a new Ed25519 keypair for witness notarization","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Chain already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WitnessInitResponse"}}}},"201":{"description":"New chain created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WitnessInitResponse"}}}}},"tags":["Witness"]}},"/v1/witness/config":{"post":{"summary":"Configure S3 storage","description":"Configure S3-compatible storage for receipt delivery. Endpoint must use HTTPS and will be validated for SSRF.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageConfigRequest"}}}},"responses":{"200":{"description":"Storage configured successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"400":{"description":"Invalid configuration (non-HTTPS endpoint, SSRF detected, etc.)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Witness"]}},"/v1/witness/log":{"post":{"summary":"Notarize JSON log","description":"Submit arbitrary JSON log entry for notarization, returns signed receipt. Returns 200 when receipt delivered to S3, 202 with X-Witness-Status: buffered when stored in Purgatory.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotarizeLogRequest"}}}},"responses":{"200":{"description":"Log notarized successfully and delivered to configured S3 storage","headers":{"X-Witness-Status":{"schema":{"type":"string","enum":["delivered"]},"description":"Storage delivery status (omitted when no storage configured)"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JSONReceiptV1"}}}},"202":{"description":"Log notarized successfully but receipt buffered in Purgatory (customer S3 unavailable)","headers":{"X-Witness-Status":{"schema":{"type":"string","enum":["buffered"]},"description":"Receipt stored in Purgatory, will retry delivery"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JSONReceiptV1"}}}},"400":{"description":"Invalid log object","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Payload too large (>100KB)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Witness"]}},"/v1/witness/verify":{"post":{"summary":"Verify receipt signature","description":"Server-side verification of a JSONReceiptV1. Checks content hash, chain hash, and Ed25519 signature. Returns detailed per-check results without short-circuiting. Public — no authentication required.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyReceiptRequest"}}}},"responses":{"200":{"description":"Verification completed (valid or invalid)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationResult"}}}},"400":{"description":"Malformed receipt (missing required fields)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Payload too large (>100KB)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Witness"]}},"/v1/witness/pubkey/{chainId}":{"get":{"summary":"Get public key for a chain","description":"Returns the Ed25519 JWK public key for signature verification (no authentication required)","parameters":[{"name":"chainId","in":"path","required":true,"schema":{"type":"string","pattern":"^wc_[A-Za-z0-9_-]{32}$","example":"wc_9tQvXm2LpR4sK7nB1cD8fG3hJ5wY6zA0"},"description":"The chain_id returned by POST /v1/witness/sys/init. This is the only identifier this endpoint accepts: a verifier holds no credential, so there is no account or project to infer from the request."}],"responses":{"200":{"description":"Public key retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WitnessPublicKey"}}}},"404":{"description":"No chain has been initialized under that id"}},"tags":["Witness"]}},"/signup":{"post":{"summary":"Create account","description":"Creates a new account with free or pro tier","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupRequest"}}}},"responses":{"201":{"description":"Account created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignupResponse"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Account"]}},"/v1/latch/{key}/acquire":{"post":{"summary":"Acquire a lock","description":"Acquires a distributed lock, hanging until acquired or timeout. Metered at 1 unit per attempt.","security":[{"bearerAuth":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"description":"Lock key"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatchAcquireRequest"}}}},"responses":{"200":{"description":"Lock acquired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatchAcquireResponse"}}}},"408":{"description":"Timeout waiting for lock","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Queue full or rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Latch"]}},"/v1/latch/{key}/release":{"post":{"summary":"Release a lock","description":"Releases a held lock using the token from acquire","security":[{"bearerAuth":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"description":"Lock key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatchReleaseRequest"}}}},"responses":{"200":{"description":"Lock released","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatchReleaseResponse"}}}},"400":{"description":"Invalid token or lock not held","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Latch"]}},"/v1/latch/{key}":{"get":{"summary":"Get lock status","description":"Returns current lock status (free, no metering)","security":[{"bearerAuth":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"description":"Lock key"}],"responses":{"200":{"description":"Lock status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LatchStatusResponse"}}}}},"tags":["Latch"]}},"/v1/pulse":{"get":{"summary":"List monitors","description":"List all monitors with optional status filter","security":[{"bearerAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["up","down","unknown"]},"description":"Filter by status"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Maximum monitors to return"},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Pagination cursor"}],"responses":{"200":{"description":"Monitor list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListMonitorsResponse"}}}}},"tags":["Pulse"]},"post":{"summary":"Create monitor","description":"Create a new uptime monitor","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMonitorRequest"}}}},"responses":{"201":{"description":"Monitor created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorResponse"}}}}},"tags":["Pulse"]}},"/v1/pulse/{monitorId}":{"get":{"summary":"Get monitor status","description":"Get current status for a monitor","security":[{"bearerAuth":[]}],"parameters":[{"name":"monitorId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Monitor ID"}],"responses":{"200":{"description":"Monitor status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorStatusResponse"}}}},"404":{"description":"Monitor not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Pulse"]},"put":{"summary":"Update monitor","description":"Update monitor configuration","security":[{"bearerAuth":[]}],"parameters":[{"name":"monitorId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Monitor ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMonitorRequest"}}}},"responses":{"200":{"description":"Monitor updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorResponse"}}}},"404":{"description":"Monitor not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Pulse"]},"delete":{"summary":"Delete monitor","description":"Delete a monitor","security":[{"bearerAuth":[]}],"parameters":[{"name":"monitorId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Monitor ID"}],"responses":{"200":{"description":"Monitor deleted","content":{"application/json":{"schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean"}}}}}},"404":{"description":"Monitor not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Pulse"]}},"/v1/pulse/{monitorId}/check":{"post":{"summary":"Check now","description":"Execute immediate check (no notifications)","security":[{"bearerAuth":[]}],"parameters":[{"name":"monitorId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Monitor ID"}],"responses":{"200":{"description":"Check executed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MonitorStatusResponse"}}}},"404":{"description":"Monitor not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"tags":["Pulse"]}},"/v1/keys":{"post":{"summary":"Create API key","description":"Create a new API key with specified scopes. The prefix decides what the key is for. A key minted with prefix \"sm\" is a Solenoid credential and authenticates against this API. Any other prefix produces a key for your own application: Solenoid will not accept it as a credential, and you verify it yourself with POST /v1/keys/verify, which returns the scopes you attached.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyRequest"}}}},"responses":{"201":{"description":"Key created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyResponse"}}}}},"tags":["Key"]},"get":{"summary":"List API keys","description":"List all API keys for the project","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Keys retrieved successfully","content":{"application/json":{"schema":{"type":"object","required":["keys"],"properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/CreateKeyResponse"}},"has_more":{"type":"boolean"},"next_cursor":{"type":"string","nullable":true}}}}}}},"tags":["Key"]}},"/v1/keys/{id}":{"get":{"summary":"Get key metadata","description":"Get metadata for a specific API key","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Key metadata retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeyResponse"}}}}},"tags":["Key"]},"delete":{"summary":"Revoke API key","description":"Revoke an API key immediately","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Key revoked successfully","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"}}}}}}},"tags":["Key"]}},"/v1/keys/{id}/rotate":{"post":{"summary":"Rotate API key","description":"Rotate an API key with grace period","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Key rotated successfully","content":{"application/json":{"schema":{"type":"object","required":["new_secret","old_expires_at","grace_hours"],"properties":{"new_secret":{"type":"string"},"old_expires_at":{"type":"string","format":"date-time"},"grace_hours":{"type":"integer"}}}}}}},"tags":["Key"]}},"/v1/keys/verify":{"post":{"summary":"Verify API key","description":"Verify an API key secret","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyKeyRequest"}}}},"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyKeyResponse"}}}}},"tags":["Key"]}},"/v1/projects":{"get":{"summary":"List projects","description":"List all projects for the authenticated user","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":10},"description":"Maximum number of projects to return"},{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Pagination cursor"}],"responses":{"200":{"description":"Projects retrieved successfully","content":{"application/json":{"schema":{"type":"object","required":["projects"],"properties":{"projects":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"cursor":{"type":"string","description":"Pagination cursor for next page"}}}}}}},"tags":["Projects"]},"post":{"summary":"Create project","description":"Create a new project","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Project name"},"description":{"type":"string","maxLength":500,"description":"Project description"}}}}}},"responses":{"201":{"description":"Project created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}}},"tags":["Projects"]}},"/v1/projects/{id}":{"get":{"summary":"Get project","description":"Get project details","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"Project retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}}},"tags":["Projects"]},"patch":{"summary":"Update project","description":"Update project name or description","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"Project name"},"description":{"type":"string","maxLength":500,"nullable":true,"description":"Project description"}}}}}},"responses":{"200":{"description":"Project updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Project"}}}}},"tags":["Projects"]},"delete":{"summary":"Delete project","description":"Soft-delete a project","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"Project deleted successfully","content":{"application/json":{"schema":{"type":"object","required":["success","deleted_at"],"properties":{"success":{"type":"boolean"},"deleted_at":{"type":"string","format":"date-time"}}}}}}},"tags":["Projects"]}},"/v1/projects/{id}/overview":{"get":{"summary":"Get project overview","description":"Get project resource overview and usage statistics","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project ID"}],"responses":{"200":{"description":"Project overview retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectOverview"}}}}},"tags":["Projects"]}},"/v1/projects/{id}/analytics":{"get":{"summary":"Get project analytics","description":"Per-project usage analytics via Meter aggregation","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Project ID"},{"name":"start","in":"query","schema":{"type":"string","format":"date-time"},"description":"Start of analytics period"},{"name":"end","in":"query","schema":{"type":"string","format":"date-time"},"description":"End of analytics period"}],"responses":{"200":{"description":"Analytics retrieved successfully","content":{"application/json":{"schema":{"type":"object","required":["project_id","period","usage"],"properties":{"project_id":{"type":"string","description":"Project ID"},"period":{"type":"object","required":["start","end"],"properties":{"start":{"type":"string","format":"date-time","description":"Period start"},"end":{"type":"string","format":"date-time","description":"Period end"}}},"usage":{"type":"object","additionalProperties":{"type":"integer"},"description":"Usage metrics by service"}}}}}}},"tags":["Projects"]}}},"tags":[{"name":"Projects","description":"Project management endpoints"},{"name":"Meter","description":"Usage metering endpoints"},{"name":"Meter - Multi-Tenant","description":"Manage end-user meters"},{"name":"Relay","description":"Webhook scheduling endpoints"},{"name":"Witness","description":"Digital notary endpoints"},{"name":"Account","description":"Account management"},{"name":"Contact","description":"Contact form"},{"name":"Latch","description":"Distributed lock endpoints"},{"name":"Pulse","description":"Uptime monitoring endpoints"},{"name":"Key","description":"API key management endpoints"}]}