{"openapi":"3.1.0","info":{"title":"WAYSCloud Public API","version":"1.0.0","description":"Programmatic access to all WAYSCloud services. Base URL: `https://api.wayscloud.services`\n\nThe API is organized into three layers:\n\n**Account API** — Manage your profile, API keys, SSH keys, and view active services. Uses Personal Access Tokens (PAT) or OIDC.\n\n**Management API** — Provision and control resources: VPS, container apps, databases, storage buckets, DNS zones, IoT devices. Uses Service API Keys.\n\n**Service API** — Runtime operations: LLM chat (OpenAI-compatible), S3 storage, telemetry. Uses Service Keys or AWS SigV4.\n\n**Quick start:** Create a Service API Key via dashboard or `POST /v1/account/api-keys/service`, then provision resources and start using them.\n"},"servers":[{"url":"https://api.wayscloud.services","description":"Production"}],"tags":[{"name":"Account","description":"Profile, API keys, SSH keys, and service overview"},{"name":"VPS","description":"Virtual Private Servers"},{"name":"Databases","description":"PostgreSQL and MariaDB instances"},{"name":"Storage","description":"S3-compatible object storage"},{"name":"DNS","description":"DNS zones and records"},{"name":"Apps","description":"Container App Platform. Deploy and manage container apps with scaling controls, scale-to-zero, persistent storage, and HTTPS with automated certificate management."},{"name":"LLM","description":"AI language models (OpenAI-compatible). Reasoning-capable models may return extended metadata in `model_output_metadata.reasoning` containing inference traces, without changing the request format."},{"name":"SMS","description":"Send SMS messages and manage inbound keywords. Pay-as-you-go pricing."},{"name":"SMS Keywords","description":"Reserve keywords for inbound SMS routing."},{"name":"Contacts","description":"Shared contact directory for SMS, Email and other services. Manage contacts and groups for batch operations."},{"name":"Regions","description":"Available service regions"},{"name":"Domain Verification","description":"Verify domain ownership via DNS for email sending, custom hostnames, webhooks, and more."},{"name":"Verify","description":"Multi-channel OTP/2FA verification service.\n\n**Channels:** SMS, Voice (TTS), and Email\n\n**Quick Start:**\n```bash\n# SMS\ncurl -X POST https://api.wayscloud.services/v1/verify/start \\\n  -H \"X-API-Key: wayscloud_verify_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"channel\": \"sms\", \"recipient\": \"+4712345678\", \"locale\": \"no\"}'\n\n# Voice (TTS call)\ncurl -X POST https://api.wayscloud.services/v1/verify/start \\\n  -H \"X-API-Key: wayscloud_verify_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"channel\": \"voice\", \"recipient\": \"+4712345678\", \"locale\": \"no\"}'\n\n# Email\ncurl -X POST https://api.wayscloud.services/v1/verify/start \\\n  -H \"X-API-Key: wayscloud_verify_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"channel\": \"email\", \"recipient\": \"user@example.com\", \"locale\": \"no\"}'\n\n# Check code\ncurl -X POST https://api.wayscloud.services/v1/verify/check \\\n  -H \"X-API-Key: wayscloud_verify_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"session_id\": \"...\", \"code\": \"123456\"}'\n```\n\n**Channel Details:**\n\n| Channel | Recipient | Sender |\n|---------|-----------|--------|\n| `sms` | Phone (E.164) | Configurable alphanumeric |\n| `voice` | Phone (E.164) | +47 21 53 50 30 |\n| `email` | Email address | Verified domain or WAYSCloud default |\n\n**Pricing:** 1.25 NOK per session + delivery (SMS ~0.35 NOK, Voice ~0.50 NOK, Email included)\n\n**Security:**\n- Codes hashed with SHA256 (never stored in plaintext)\n- Max 5 attempts per session\n- TTL: 60-900 seconds (configurable)\n- Tenant isolation"},{"name":"GPU Studio","description":"Generate images and short video clips using WAYSCloud GPU Studio.\n\nGPU Studio is a job-based API:\n- Create a job with an engine and input prompt\n- Poll job status until it is completed\n- Download results via URLs in output_files\n\nEngines are curated and stable identifiers. Use GET /v1/gpu/engines to discover which engines are available for your account.\n\nPricing is not hardcoded in this documentation. Always use GET /v1/gpu/pricing for the current price list (supports multiple currencies).\n\n**Authentication:** Use either `X-API-Key` header or `Authorization: Bearer <key>` for protected endpoints."},{"name":"Speech Intelligence","description":"Transcribe audio and video files using WAYSCloud Speech Intelligence.\n\n**How it works:**\n1. `POST /v1/transcript/jobs` — create a job, receive a presigned upload URL\n2. Upload your file to the presigned URL (HTTP PUT, raw file body)\n3. `POST /v1/transcript/jobs/{job_id}/upload-complete` — confirm upload and start processing\n4. `GET /v1/transcript/jobs/{job_id}` — poll until status is `ready`\n5. Read segments from the response, or use the export endpoint for SRT/VTT\n\n**Job statuses:**\n| Status | Description |\n|--------|-------------|\n| `created` | Job created, waiting for file upload |\n| `queued` | Upload confirmed, waiting for processing |\n| `processing` | Transcription in progress |\n| `ready` | Complete — segments and default exports (TXT, JSON) available |\n| `failed` | Processing failed (see `error_message`) |\n| `expired` | Upload URL expired (file was never uploaded within 1 hour) |\n\n**Default exports:** `txt` and `json` are generated automatically when a job completes. `srt` and `vtt` can be generated on demand via `POST /v1/transcript/jobs/{job_id}/export`.\n\n**Supported inputs:** Any file with an audio track — common containers include MP3, WAV, M4A, OGG, FLAC, WEBM, and MP4. Files are validated server-side using ffprobe; the file must contain at least one audio stream. Maximum file size: 2 GB.\n\n**Languages:** Set `language` to `auto` (default) for automatic detection, or specify an ISO 639-1 code (e.g., `no`, `en`, `sv`, `da`, `fr`, `de`).\n\n**Pricing:** Per-minute billing based on audio duration. See your dashboard for current rates.\n\n**Authentication:** WAYSCloud API key via `X-API-Key` header or `Authorization: Bearer <key>`. Keys are created in the WAYSCloud dashboard under Account → API Keys."},{"name":"Chatbot","description":"Integrate WAYSCloud chatbots into your applications.\n\nBuild AI-powered chatbots with your own knowledge sources, then access them programmatically from any backend, mobile app, or integration.\n\n**Quick start:**\n```bash\n# List your chatbots\ncurl https://api.wayscloud.services/v1/chatbot/api/bots \\\n  -H \"X-API-Key: wayscloud_api_xxx\"\n\n# Send a message\ncurl -X POST https://api.wayscloud.services/v1/chatbot/api/chat \\\n  -H \"X-API-Key: wayscloud_api_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"bot_id\": \"my-bot\", \"message\": \"What are your prices?\"}'\n```\n\n**Streaming:** Use `/v1/chatbot/api/chat/stream` for real-time token-by-token responses via Server-Sent Events.\n\n**Sessions:** Omit `session_id` to start a new conversation. Include it to continue an existing one.\n\n**Authentication:** API key with `chatbot` permission, sent via `X-API-Key` header.\n\n**Manage chatbots:** Create and configure chatbots, add knowledge sources, and customize behavior in the [WAYSCloud Dashboard](https://my.wayscloud.services/chatbot)."},{"name":"Live Events","description":"Managed live streaming and event broadcasting.\n\nCreate events, manage stream keys and access tokens, control lifecycle (rehearsal → live → stop), and retrieve recordings, stats, and health. A separate set of public viewer endpoints resolves watch state for watch pages and embeds.\n\n---\n\n### Management endpoints (authenticated)\n\n**Authentication:** WAYSCloud API key or Keycloak JWT via `Authorization: Bearer` header or `X-API-Key` header.\n\n**Event lifecycle:**\n\n| Transition | Endpoint | Precondition |\n|------------|----------|-------------|\n| Create event | `POST /v1/live-events/events` | — |\n| Start rehearsal | `POST /v1/live-events/events/{id}/rehearsal/start` | Status: `ready` |\n| Go live | `POST /v1/live-events/events/{id}/live/start` | Status: `ready` or `rehearsal` |\n| Stop | `POST /v1/live-events/events/{id}/live/stop` | Status: `live` or `rehearsal` |\n| Rotate stream key | `POST /v1/live-events/events/{id}/stream-key/rotate` | Not while `live` |\n| Update | `PATCH /v1/live-events/events/{id}` | Status: `draft`, `scheduled`, or `ready` |\n| Delete | `DELETE /v1/live-events/events/{id}` | Not while `live`, `ending`, or `processing` |\n\n**RTMP ingest:** `rtmp://ingest.wayscloud.services/live` with the stream key from event creation.\n\n---\n\n### Viewer endpoints (public, no authentication)\n\nResolve event watch state for watch pages, embeds, and custom players. Verify viewer access for password- or token-protected events.\n\nThe resolve endpoint returns two state fields:\n- `watch_state` — canonical viewer-facing UI state (`not_started`, `access_required`, `live`, `replay_available`, etc.)\n- `status` — internal lifecycle state for diagnostics\n\n**Access control modes:**\n\n| Mode | Behavior |\n|------|----------|\n| `public` | Anyone with the URL can watch. Playback URLs returned directly. |\n| `password` | Viewer submits password → receives access grant + playback. |\n| `token` | Viewer submits pre-shared token → receives access grant. Token consumed on playback start, not on verify. |\n| `tenant_only` | Viewer must be authenticated as a member of the same customer account. |\n\n**Watch pages:** `https://live.wayscloud.services/watch/{slug}`\n**Embed:** `https://live.wayscloud.services/embed/{slug}`\n\n---\n\n**Manage events in dashboard:** [my.wayscloud.services/live-events](https://my.wayscloud.services/live-events)"},{"name":"IP Intelligence","description":"Real-time IP threat scoring, geolocation, and network intelligence.\n\n**Authentication:** WAYSCloud API key via `X-API-Key` header. Free tier auto-provisioned on first use — no manual activation needed.\n\n**Free tier:** 1,000 requests/day. Upgrade via [dashboard](https://my.wayscloud.services).\n\n| Endpoint | Description |\n|----------|-------------|\n| `GET /v1/ip/{ip}` | Full summary (geo + network + threat + flags) |\n| `GET /v1/ip/{ip}/geo` | Geolocation + rDNS + ASN |\n| `GET /v1/ip/{ip}/threat` | Threat score + categories + flags |\n| `GET /v1/ip/threats/live` | Live threat feed |\n| `GET /v1/ip/countries/{code}` | Country intelligence |\n| `GET /v1/ip/asn/{asn}` | ASN intelligence |\n| `POST /v1/ip/report` | Submit IP abuse report |\n| `POST /v1/ip/reporters/register` | Register as abuse reporter |\n| `POST /v1/ip/reporters/verify` | Verify reporter domain (DNS TXT) |\n| `POST /v1/ip/delist` | Request IP delisting |\n\n**Docs:** [docs.wayscloud.services/api/ip-intelligence](https://docs.wayscloud.services/api/ip-intelligence)\n**Portal:** [ip.wayscloud.services](https://ip.wayscloud.services)"},{"name":"IoT Platform","description":"Manage IoT devices, fleet operations, alarm rules, telemetry, and notifications.\n\n**Authentication:** `X-API-Key` header with `iot` service permission.\n\n**Quick Start:**\n```bash\n# Register a device\ncurl -X POST https://api.wayscloud.services/v1/iot/devices \\\n  -H \"X-API-Key: wayscloud_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"Sensor A\", \"device_type\": \"ESP32\"}'\n\n# List alarms\ncurl https://api.wayscloud.services/v1/iot/alarms?status=active \\\n  -H \"X-API-Key: wayscloud_xxx\"\n\n# Create webhook notification channel\ncurl -X POST https://api.wayscloud.services/v1/iot/notifications/channels \\\n  -H \"X-API-Key: wayscloud_xxx\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"Ops Webhook\", \"channel_type\": \"webhook\", \"config\": {\"url\": \"https://example.com/hook\"}}'\n```\n\n**Resources:** Devices, Groups, Profiles, Rules, Alarms, Telemetry, Notifications"}],"paths":{"/v1/account/profile":{"get":{"tags":["Account"],"summary":"Get profile","description":"Retrieve your account profile with customer ID, email, and account type.","operationId":"getAccountProfile","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"Account profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountProfile"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Account not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Account not found"}}}}}}},"/v1/account/api-keys":{"get":{"tags":["Account"],"summary":"List API keys","description":"List all API keys (service keys and PATs) on your account. Use to audit keys or find keys to revoke.","operationId":"listApiKeys","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"API keys with metadata","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/APIKeyItem"},"example":[{"id":"a8b9c0d1-e2f3-4567-89ab-cdef01234567","key_prefix":"wayscloud_2xK9mPq","key_type":"service","service":"database","description":"Production database access","scopes":["database:read","database:write"],"is_active":true,"created_at":"2024-03-01T14:00:00Z","last_used_at":"2024-03-15T08:45:22Z"}]}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}}},"/v1/account/api-keys/service":{"post":{"tags":["Account"],"summary":"Create service key","description":"Create a service API key for accessing databases, storage, VPS, DNS, IoT, or LLM. The full key is shown only once—store it securely.","operationId":"createServiceKey","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateServiceKeyRequest"}}}},"responses":{"200":{"description":"API key created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyCreated"}}}},"400":{"description":"Invalid service type","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid service type. Valid options: database, storage, llm-api, dns, vps"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}}},"/v1/account/api-keys/pat":{"post":{"tags":["Account"],"summary":"Create PAT","description":"Create a Personal Access Token for account management and automation. Choose scopes carefully. The full token is shown only once.","operationId":"createPat","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePATRequest"}}}},"responses":{"200":{"description":"PAT created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/APIKeyCreated"}}}},"400":{"description":"Invalid scopes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid scopes. See GET /v1/account/api-keys for valid scope names"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}}},"/v1/account/api-keys/{key_id}":{"delete":{"tags":["Account"],"summary":"Revoke API key","description":"Permanently revoke an API key or PAT. Takes effect immediately. This action cannot be undone.","operationId":"revokeApiKey","security":[{"AccountAuth":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Key ID from list endpoint"}],"responses":{"200":{"description":"Key revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Key not found"}}}}}}},"/v1/account/ssh-keys":{"get":{"tags":["Account"],"summary":"List SSH keys","description":"List all SSH public keys registered for VPS access. Shows fingerprint and usage count.","operationId":"listSshKeys","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"SSH keys","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SSHKeyItem"},"example":[{"id":"d1e2f3a4-b5c6-7890-abcd-ef1234567890","name":"deploy-server","fingerprint":"SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8","key_type":"ssh-ed25519","created_at":"2024-02-20T11:15:00Z","vm_count":3}]}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}},"post":{"tags":["Account"],"summary":"Add SSH key","description":"Register a new SSH public key. Supports RSA, Ed25519, and ECDSA. Keys are automatically deployed to new VPS instances.","operationId":"createSshKey","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSSHKeyRequest"}}}},"responses":{"200":{"description":"SSH key added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SSHKeyItem"}}}},"400":{"description":"Invalid key format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid SSH key format. Supported types: ssh-rsa, ssh-ed25519, ecdsa-sha2-nistp256"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"409":{"description":"Key already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"SSH key with this fingerprint is already registered"}}}}}}},"/v1/account/ssh-keys/{key_id}":{"delete":{"tags":["Account"],"summary":"Delete SSH key","description":"Remove an SSH key from your account. Existing VPS instances keep the key until manually removed or recreated.","operationId":"deleteSshKey","security":[{"AccountAuth":[]}],"parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Key ID"}],"responses":{"200":{"description":"Key deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Key not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Key not found"}}}}}}},"/v1/account/services":{"get":{"tags":["Account"],"summary":"List services","description":"Get all your active services: databases, storage buckets, VPS instances, DNS zones, IoT devices, and GlobalSIM cards.","operationId":"listServices","security":[{"AccountAuth":[]}],"responses":{"200":{"description":"Active services","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ServiceItem"},"example":[{"id":"f3a4b5c6-d7e8-9012-cdef-345678901234","service_type":"database","name":"webapp-prod-db","status":"running","region":"oslo","created_at":"2024-02-01T10:00:00Z","metadata":{"engine":"postgresql","version":"16"}}]}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}}}}},"/v1/databases":{"post":{"summary":"Create database","description":"Create a new database (PostgreSQL or MariaDB)\n\nThis endpoint:\n1. Authenticates the customer via API key\n2. Checks database quota and permissions\n3. Creates a database and user on the local server\n4. Reports usage to the central server for billing\n5. Returns connection details\n\nRequires: 'database:create' permission\n\nSpecial behavior for internal API keys:\n- Internal keys can specify customer_id in request body to create databases on behalf of customers\n- Regular customer keys can only create databases for themselves (customer_id override is ignored)","operationId":"create_database_v1_databases_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseCreateResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Database name conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database with this name already exists"}}}}},"security":[{"ServiceAuth":[]}],"tags":["Databases"]},"delete":{"summary":"Delete database","description":"Delete a database (PostgreSQL or MariaDB)\n\nThis endpoint:\n1. Authenticates the customer via API key\n2. Checks permissions\n3. Verifies database ownership\n4. Deletes the database and optionally the user\n5. Reports deletion to the central server\n\nRequires: 'database:delete' permission and database ownership","operationId":"delete_database_v1_databases_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseDeleteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseDeleteResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}],"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}":{"delete":{"summary":"Delete database","description":"Delete a database (RESTful endpoint with path parameters)\n\nThis is an alternative RESTful endpoint that accepts database info via URL path\ninstead of request body. It calls the same underlying delete logic.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    username: Optional username to delete (query parameter)\n\nRequires: 'database:delete' permission and database ownership","operationId":"delete_database_restful_v1_databases__db_type___db_name__delete","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"username","in":"query","required":false,"schema":{"type":"string","title":"Username"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseDeleteResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]},"patch":{"tags":["Databases"],"summary":"Update database","description":"Update database settings (scaling)\n\nCurrently supports:\n- **connection_limit**: Adjust the maximum number of simultaneous connections\n- **description**: Add or update a description/notes for this database\n\nOther scaling options (RAM, storage) require support intervention\nas they involve infrastructure changes.","operationId":"scale_database_v1_databases__db_type___db_name__patch","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseScaleRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationSuccess"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}":{"get":{"summary":"List databases by type","description":"List all databases of a specific type\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    customer_id: Optional query parameter for internal keys to filter by specific customer\n\nRequires: 'database:read' permission\n\nSpecial behavior for internal API keys:\n- Internal keys can specify customer_id query parameter to list databases for specific customer\n- Regular customer keys can only list their own databases (customer_id parameter is ignored)","operationId":"list_databases_v1_databases__db_type__get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseListResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"tags":["Databases"]}},"/v1/databases/engines":{"get":{"tags":["Databases"],"summary":"List database engines","description":"List all supported database engines\n\nReturns available database types with their versions and capabilities.\nRequires service API key.","operationId":"list_database_engines_v1_databases_engines_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseEngineList"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/databases/{db_type}/{db_name}/restart":{"post":{"tags":["Databases"],"summary":"Restart database","description":"Terminate all active connections to a database\n\nThis is useful when you need to force-disconnect all clients,\nfor example before maintenance or schema changes.\n\n**Warning:** This will immediately disconnect all active clients.\nApplications should be prepared to reconnect automatically.\n\nNote: This doesn't restart the database server itself, only\nterminates connections to this specific database.","operationId":"restart_database_connections_v1_databases__db_type___db_name__restart_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationSuccess"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/credentials":{"get":{"tags":["Databases"],"summary":"Get database credentials","description":"Get database credentials metadata (without password)\n\nReturns connection info for the database without exposing the password.\nTo get a new password, use POST /credentials/rotate.\n\n**Security Note:** This endpoint intentionally does NOT return the password.\nPasswords are only shown when first created or when rotated.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nRequires: 'database:read' permission and database ownership","operationId":"get_database_credentials_v1_databases__db_type___db_name__credentials_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseCredentials"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/credentials/rotate":{"post":{"tags":["Databases"],"summary":"Rotate database credentials","description":"Rotate database password and return new credentials\n\nGenerates a new secure password for the database user and updates\nit in both the database server and Vault.\n\n**Warning:** This will invalidate the old password immediately.\nAll existing connections using the old password will fail.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nReturns:\n    New credentials including the new password and connection string","operationId":"rotate_database_credentials_v1_databases__db_type___db_name__credentials_rotate_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseCredentialsResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/info":{"get":{"summary":"Get database info","description":"Get detailed information about a database\n\nReturns:\n- Database size (MB)\n- Current connection count\n- Connection limit\n- Owner\n- Creation timestamp (if available from Vault)\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nRequires: 'database:read' permission and database ownership","operationId":"get_database_info_v1_databases__db_type___db_name__info_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseInfoResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}/firewall":{"post":{"tags":["Databases"],"summary":"Add firewall rule","description":"Add an IP address to the firewall whitelist for a specific database\n\nCreates a UFW firewall rule to allow the specified IP address to connect\nto this database. Rules are applied immediately at the network level.\n\n**Path Parameters:**\n- db_type: Database type (postgresql or mariadb)\n- db_name: Technical database name (e.g., cust_abc123_pg_20251109_xyz)\n\n**Request Body:**\n- ip_address: IPv4 address or CIDR block (e.g., \"192.168.1.1\" or \"10.0.0.0/24\")\n- description: Optional description for the rule\n\n**Note:** The database must belong to the authenticated customer.","operationId":"add_firewall_rule_v1_databases__db_type___db_name__firewall_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirewallAddRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirewallRuleResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}},"get":{"tags":["Databases"],"summary":"List firewall rules","description":"List all firewall rules for a specific database\n\nReturns all IP addresses that are whitelisted for this database.\n\n**Path Parameters:**\n- db_type: Database type (postgresql or mariadb)\n- db_name: Technical database name\n\n**Note:** The database must belong to the authenticated customer.","operationId":"get_database_firewall_rules_v1_databases__db_type___db_name__firewall_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FirewallListResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/firewall/{rule_id}":{"delete":{"tags":["Databases"],"summary":"Delete firewall rule","description":"Remove a specific firewall rule from a database\n\nRemoves the UFW firewall rule and deletes it from the database.\nThe rule is verified to be removed before updating the database.\n\n**Path Parameters:**\n- db_type: Database type (postgresql or mariadb)\n- db_name: Technical database name\n- rule_id: UUID of the firewall rule to delete\n\n**Note:** The database must belong to the authenticated customer.","operationId":"delete_firewall_rule_v1_databases__db_type___db_name__firewall__rule_id__delete","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationSuccess"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/databases/{db_type}/{db_name}/metrics":{"get":{"tags":["Databases"],"summary":"Get database metrics","description":"Get performance metrics for a specific database\n\nReturns storage usage, connection counts, and query statistics.\nData is pulled from the central metrics database.\n\n**Note:** Metrics are collected every 5 minutes by the background collector.","operationId":"get_database_metrics_v1_databases__db_type___db_name__metrics_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"enum":["postgresql","mariadb"],"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseMetrics"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}}}},"/v1/quota":{"get":{"tags":["Databases"],"summary":"Get database quota","description":"Get database quota and usage summary for customer\n\nShows RAM allocation, storage usage, and estimated monthly cost.","operationId":"get_database_quota_v1_quota_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatabaseQuota"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/databases/history":{"get":{"tags":["Databases"],"summary":"List database history","description":"List database history (for billing and audit)\n\nShows all databases ever created by the customer, including deleted ones.\nUseful for billing reconciliation and audit purposes.","operationId":"list_database_history_v1_databases_history_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","description":"Customer ID for internal API calls","title":"Customer Id"},"description":"Customer ID for internal API calls"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DatabaseHistory"}}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/databases/{db_type}/{db_name}/snapshots":{"post":{"summary":"Create snapshot","description":"Create a snapshot (backup) of a database\n\nThis creates an immediate backup of the database.\nSnapshots are stored on disk and can be restored later.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    request: Snapshot creation request (optional description)\n\nRequires: 'database:read' permission and database ownership","operationId":"create_snapshot_v1_databases__db_type___db_name__snapshots_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotCreateResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]},"get":{"summary":"List snapshots","description":"List all snapshots for a database\n\nReturns all snapshots (backups) created for this database,\nsorted by creation time (newest first).\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nRequires: 'database:read' permission and database ownership","operationId":"list_snapshots_v1_databases__db_type___db_name__snapshots_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotListResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}/restore":{"post":{"summary":"Restore from snapshot","description":"Restore a database from a snapshot\n\nThis replaces the current database contents with the snapshot.\nWARNING: This operation cannot be undone! Consider creating a snapshot first.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    request: Restore request (snapshot_id, optional target_db_name)\n\nRequires: 'database:update' permission and database ownership","operationId":"restore_snapshot_v1_databases__db_type___db_name__restore_post","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotRestoreRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotRestoreResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}/snapshots/{snapshot_id}":{"delete":{"summary":"Delete snapshot","description":"Delete a snapshot\n\nPermanently deletes a snapshot file from disk.\nThis operation cannot be undone.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    snapshot_id: Snapshot ID to delete\n\nRequires: 'database:delete' permission and database ownership","operationId":"delete_snapshot_v1_databases__db_type___db_name__snapshots__snapshot_id__delete","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}},{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"string","title":"Snapshot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OperationSuccess"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/databases/{db_type}/{db_name}/backup-policy":{"get":{"summary":"Get backup policy","description":"Get backup policy for a database\n\nReturns the automated backup configuration for this database.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n\nRequires: 'database:read' permission and database ownership","operationId":"get_backup_policy_v1_databases__db_type___db_name__backup_policy_get","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicyResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]},"put":{"summary":"Set backup policy","description":"Set backup policy for a database\n\nConfigure automated backups for this database.\n\nArgs:\n    db_type: Either 'postgresql' or 'mariadb'\n    db_name: Database name (with customer prefix)\n    request: Backup policy configuration\n\nRequires: 'database:update' permission and database ownership","operationId":"set_backup_policy_v1_databases__db_type___db_name__backup_policy_put","security":[{"ServiceAuth":[]}],"parameters":[{"name":"db_type","in":"path","required":true,"schema":{"type":"string","title":"Db Type"}},{"name":"db_name","in":"path","required":true,"schema":{"type":"string","title":"Db Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupPolicyResponse"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":[{"loc":["path","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Database not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Database not found"}}}}},"tags":["Databases"]}},"/v1/vps":{"get":{"tags":["VPS"],"summary":"List VPS","description":"Get all VPS instances with hostname, IP addresses, power state, and resource allocation.","operationId":"listVps","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"VPS instances","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSList"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}},"post":{"tags":["VPS"],"summary":"Create VPS","description":"Provision a new VPS. Takes 1-3 minutes. Include SSH keys for passwordless access.","operationId":"createVps","security":[{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["hostname","plan_code","region","os_template"],"properties":{"hostname":{"type":"string","description":"Server hostname (FQDN recommended)","example":"web01.example.com"},"plan_code":{"type":"string","description":"VPS plan code (from /v1/vps/plans)","example":"vps-4gb-2cpu"},"region":{"type":"string","description":"Datacenter region (see GET /v1/vps/regions for available options)","example":"oslo"},"os_template":{"type":"string","description":"Operating system template (from /v1/vps/os-templates)","example":"ubuntu-22.04"},"display_name":{"type":"string","description":"User-friendly name for dashboard display","example":"Production Web Server"},"ssh_keys":{"type":"array","items":{"type":"string"},"description":"SSH public keys for server access (recommended)"}}},"example":{"hostname":"web01.example.com","plan_code":"vps-4gb-2cpu","region":"oslo","os_template":"ubuntu-22.04","display_name":"Production Web Server","ssh_keys":["ssh-ed25519 AAAA... user@laptop"]}}}},"responses":{"201":{"description":"VPS provisioning started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSCreated"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid plan_code. See GET /v1/vps/plans for available options"}}}},"402":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient balance. Top up at my.wayscloud.services/billing"}}}},"409":{"description":"Hostname conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Hostname already in use by another VPS"}}}}}}},"/v1/vps/plans/":{"get":{"tags":["VPS"],"summary":"List plans","description":"Get available VPS configurations with vCPU, RAM, storage, bandwidth, and pricing.","operationId":"listVpsPlans","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available plans with specifications and pricing","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"plan_code":{"type":"string","example":"vps-4gb-2cpu"},"name":{"type":"string","example":"4 GB RAM, 2 vCPU"},"vcpus":{"type":"integer","example":2},"ram_gb":{"type":"integer","example":4},"storage_gb":{"type":"integer","example":80},"bandwidth_tb":{"type":"number","example":4.0},"price":{"type":"number","description":"Monthly price in account currency","example":199.0},"currency":{"type":"string","description":"Price currency (from account settings)","example":"NOK"}}},"example":[{"plan_code":"vps-2gb-1cpu","name":"2 GB RAM, 1 vCPU","vcpus":1,"ram_gb":2,"storage_gb":40,"bandwidth_tb":2.0,"price":99.0,"currency":"NOK"},{"plan_code":"vps-4gb-2cpu","name":"4 GB RAM, 2 vCPU","vcpus":2,"ram_gb":4,"storage_gb":80,"bandwidth_tb":4.0,"price":199.0,"currency":"NOK"},{"plan_code":"vps-8gb-4cpu","name":"8 GB RAM, 4 vCPU","vcpus":4,"ram_gb":8,"storage_gb":160,"bandwidth_tb":8.0,"price":399.0,"currency":"NOK"}]}}}}}}},"/v1/vps/os-templates":{"get":{"tags":["VPS"],"summary":"List OS templates","description":"Get available operating systems for VPS provisioning: Ubuntu, Debian, Rocky, AlmaLinux, and Windows Server.","operationId":"listVpsOsTemplates","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available OS templates","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"ubuntu-22.04"},"name":{"type":"string","example":"Ubuntu 22.04 LTS"},"family":{"type":"string","example":"ubuntu"},"version":{"type":"string","example":"22.04"},"is_windows":{"type":"boolean","example":false}}},"example":[{"id":"ubuntu-24.04","name":"Ubuntu 24.04 LTS","family":"ubuntu","version":"24.04","is_windows":false},{"id":"debian-12","name":"Debian 12","family":"debian","version":"12","is_windows":false},{"id":"rocky-9","name":"Rocky Linux 9","family":"rocky","version":"9","is_windows":false},{"id":"windows-server-2025","name":"Windows Server 2025","family":"windows","version":"2025","is_windows":true}]}}}}}}},"/v1/vps/regions":{"get":{"tags":["VPS"],"summary":"List regions","description":"Get available datacenter regions. Choose the region closest to your users for best latency.","operationId":"listVpsRegions","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available regions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","example":"oslo"},"name":{"type":"string","example":"Oslo, Norway"},"country":{"type":"string","example":"NO"},"available":{"type":"boolean","example":true}}},"example":[{"id":"oslo","name":"Oslo, Norway","country":"NO","available":true},{"id":"stockholm","name":"Stockholm, Sweden","country":"SE","available":true},{"id":"frankfurt","name":"Frankfurt, Germany","country":"DE","available":true}]}}}}}}},"/v1/vps/{vps_id}":{"get":{"tags":["VPS"],"summary":"Get VPS","description":"Get complete VPS details: hostname, IP addresses, OS, plan, and configuration.","operationId":"getVps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSInstance"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/status":{"get":{"tags":["VPS"],"summary":"Get status","description":"Check power state (running/stopped), uptime, and health indicators.","operationId":"getVpsStatus","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSStatus"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/start":{"post":{"tags":["VPS"],"summary":"Start VPS","description":"Power on a stopped VPS. Accessible within 30-60 seconds.","operationId":"startVps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS starting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/stop":{"post":{"tags":["VPS"],"summary":"Stop VPS","description":"Gracefully shut down a running VPS. Stopped VPS still incurs storage costs.","operationId":"stopVps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS stopping","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/reboot":{"post":{"tags":["VPS"],"summary":"Reboot VPS","description":"Graceful reboot. Back online within 1-2 minutes.","operationId":"rebootVps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"VPS rebooting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"VPS not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"VPS not found"}}}}}}},"/v1/vps/{vps_id}/snapshots":{"get":{"tags":["VPS Snapshots"],"summary":"List snapshots","description":"List point-in-time snapshots for a VPS. Snapshots are stored on the same server node — they are **not** off-site backups.","operationId":"listVpsSnapshots","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Snapshot list","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"vps_id":{"type":"string"},"snapshots":{"type":"array","items":{"$ref":"#/components/schemas/VPSSnapshot"}}}}}}},"400":{"description":"Snapshots not supported for this VPS"},"404":{"description":"VPS not found"}}},"post":{"tags":["VPS Snapshots"],"summary":"Create snapshot","description":"Create a point-in-time snapshot of the VPS disk. Maximum 10 per VPS. Names are sanitized to alphanumeric + underscore.","operationId":"createVpsSnapshot","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSSnapshotCreate"}}}},"responses":{"201":{"description":"Snapshot created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSSnapshot"}}}},"400":{"description":"Unsupported disk format or name too short"},"409":{"description":"Duplicate name or VPS not provisioned"},"429":{"description":"Snapshot limit reached (max 10)"}}}},"/v1/vps/{vps_id}/snapshots/{snapshot_name}":{"delete":{"tags":["VPS Snapshots"],"summary":"Delete snapshot","description":"Delete a snapshot from the hypervisor and database.","operationId":"deleteVpsSnapshot","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"snapshot_name","in":"path","required":true,"schema":{"type":"string"},"description":"Snapshot name"}],"responses":{"200":{"description":"Snapshot deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"Snapshot not found"}}}},"/v1/vps/{vps_id}/snapshots/{snapshot_name}/rollback":{"post":{"tags":["VPS Snapshots"],"summary":"Rollback to snapshot","description":"**WARNING: Destructive operation.** Reverts the VPS disk to a previous snapshot state. All data written after the snapshot will be permanently lost. The VPS must be stopped.","operationId":"rollbackVpsSnapshot","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"snapshot_name","in":"path","required":true,"schema":{"type":"string"},"description":"Snapshot name"}],"responses":{"200":{"description":"Rollback completed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"409":{"description":"VPS must be stopped or snapshot not available"}}}},"/v1/vps/{vps_id}/backup-policy":{"get":{"tags":["VPS Backups"],"summary":"Get backup policy","description":"Get the automatic backup schedule for a VPS.","operationId":"getVpsBackupPolicy","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Backup policy (or null if not configured)","content":{"application/json":{"schema":{"type":"object","properties":{"policy":{"$ref":"#/components/schemas/VPSBackupPolicy"},"vps_id":{"type":"string"}}}}}}}},"put":{"tags":["VPS Backups"],"summary":"Set backup policy","description":"Configure automatic off-site backups. Backups are stored in secure cloud storage, separate from the VPS server.","operationId":"setVpsBackupPolicy","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSBackupPolicySet"}}}},"responses":{"200":{"description":"Policy saved","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"policy_id":{"type":"string"},"next_backup_at":{"type":"string","format":"date-time"}}}}}}}},"delete":{"tags":["VPS Backups"],"summary":"Delete backup policy","description":"Remove the automatic backup schedule. Existing backups are not deleted.","operationId":"deleteVpsBackupPolicy","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Policy deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"No policy found"}}}},"/v1/vps/{vps_id}/backups":{"get":{"tags":["VPS Backups"],"summary":"List backups","description":"List off-site backups for a VPS. Unlike snapshots, backups survive hardware failure.","operationId":"listVpsBackups","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Backup list","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"vps_id":{"type":"string"},"backups":{"type":"array","items":{"$ref":"#/components/schemas/VPSBackup"}}}}}}}}},"post":{"tags":["VPS Backups"],"summary":"Trigger manual backup","description":"Start an immediate off-site backup. The backup runs asynchronously — poll GET /backups to check progress.","operationId":"createVpsBackup","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"202":{"description":"Backup queued","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"backup_id":{"type":"string"},"status":{"type":"string"}}}}}},"409":{"description":"Backup already in progress"}}}},"/v1/vps/{vps_id}/backups/{backup_id}":{"delete":{"tags":["VPS Backups"],"summary":"Delete backup","description":"Permanently delete a backup from off-site storage.","operationId":"deleteVpsBackup","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"backup_id","in":"path","required":true,"schema":{"type":"string"},"description":"Backup ID"}],"responses":{"200":{"description":"Backup deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"404":{"description":"Backup not found"},"409":{"description":"Cannot delete backup in progress"}}}},"/v1/vps/{vps_id}/backups/{backup_id}/restore":{"post":{"tags":["VPS Backups"],"summary":"Restore from backup","description":"**WARNING: Destructive operation.** The current VPS disk will be completely replaced with backup data. All changes after the backup will be lost. The VPS must be stopped.","operationId":"restoreVpsBackup","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"backup_id","in":"path","required":true,"schema":{"type":"string"},"description":"Backup ID"}],"responses":{"200":{"description":"Restore queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VPSAction"}}}},"409":{"description":"VPS must be stopped or backup not available"}}}},"/v1/vps/{vps_id}/backup-usage":{"get":{"tags":["VPS Backups"],"summary":"Backup storage usage","description":"Get total backup storage consumed by this VPS.","operationId":"getVpsBackupUsage","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"}],"responses":{"200":{"description":"Usage metrics","content":{"application/json":{"schema":{"type":"object","properties":{"vps_id":{"type":"string"},"total_backups":{"type":"integer"},"total_size_gb":{"type":"number"}}}}}}}}},"/v1/vps/{vps_id}/backups/{backup_id}/download":{"get":{"tags":["VPS Backups"],"summary":"Download backup","description":"Get a time-limited download URL (1 hour). Served via storage.wayscloud.services.","operationId":"downloadVpsBackup","security":[{"ServiceAuth":[]}],"parameters":[{"name":"vps_id","in":"path","required":true,"schema":{"type":"string"},"description":"VPS ID"},{"name":"backup_id","in":"path","required":true,"schema":{"type":"string"},"description":"Backup ID"}],"responses":{"200":{"description":"Download URL","content":{"application/json":{"schema":{"type":"object","properties":{"download_url":{"type":"string","format":"uri"},"expires_in":{"type":"integer","description":"Seconds until URL expires"},"size_bytes":{"type":"integer"},"filename":{"type":"string"}}}}}},"409":{"description":"Backup not available"}}}},"/v1/apps/plans":{"get":{"tags":["Apps"],"summary":"List app plans","description":"Get available App Platform plans with CPU, memory, storage, and pricing information.","operationId":"listAppPlans","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available plans","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AppPlan"}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/regions":{"get":{"tags":["Apps"],"summary":"List app regions","description":"Get available regions for App Platform with availability status.","operationId":"listAppRegions","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Available regions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AppRegion"}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps":{"get":{"tags":["Apps"],"summary":"List apps","description":"Get a paginated list of all apps for the authenticated account.","operationId":"listApps","security":[{"ServiceAuth":[]}],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1},"description":"Page number"},{"name":"page_size","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100},"description":"Items per page"}],"responses":{"200":{"description":"Paginated app list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppList"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Apps"],"summary":"Create app","description":"Create a new container app. Only `name` is required — all other fields have sensible defaults.","operationId":"createApp","security":[{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":3,"maxLength":100,"description":"Display name for the app"},"slug":{"type":"string","minLength":3,"maxLength":63,"nullable":true,"description":"URL slug. Auto-generated from name if omitted. Lowercase letters, numbers, and hyphens only."},"region":{"type":"string","default":"no","description":"Region code (from GET /v1/apps/regions)"},"plan":{"type":"string","nullable":true,"description":"Plan ID (from GET /v1/apps/plans). Uses account default if omitted."},"port":{"type":"integer","default":8080,"minimum":1,"maximum":65535,"description":"Port the app listens on"},"health_check_path":{"type":"string","default":"/health","maxLength":255,"description":"Health check endpoint path"},"env_vars":{"type":"object","additionalProperties":{"type":"string"},"nullable":true,"description":"Environment variables as key-value pairs"}}},"example":{"name":"invoice-api-prod","region":"no","plan":"app-starter","port":3000,"env_vars":{"NODE_ENV":"production"}}}}},"responses":{"201":{"description":"App created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"App quota exceeded for your plan","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/apps/{app_id}":{"get":{"tags":["Apps"],"summary":"Get app","description":"Get full details of a specific app including configuration, deployment status, and scaling settings.","operationId":"getApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Apps"],"summary":"Update app","description":"Update app configuration. All fields are optional — only provided fields are updated.\n\n**Mutable fields:** `name`, `port`, `health_check_path`, `min_instances`, `max_instances`, `env_vars`, `scale_to_zero_enabled`, `idle_timeout_minutes`\n\n**Not mutable:** `plan`, `region`, `slug` cannot be changed after creation.\n\n**Warning:** Providing `env_vars` replaces the entire environment variable set for the app. To add a single variable, first GET the current app, merge your changes, then PATCH with the full set.","operationId":"updateApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":3,"maxLength":100},"port":{"type":"integer","minimum":1,"maximum":65535},"health_check_path":{"type":"string","maxLength":255},"min_instances":{"type":"integer","minimum":0,"maximum":10,"description":"Minimum instances. Set to 0 to allow scale-to-zero."},"max_instances":{"type":"integer","minimum":1,"maximum":10},"env_vars":{"type":"object","additionalProperties":{"type":"string"},"description":"Replaces the entire environment variable set. Not a merge — omitted variables are removed."},"scale_to_zero_enabled":{"type":"boolean","description":"Enable scale-to-zero when app is idle"},"idle_timeout_minutes":{"type":"integer","minimum":1,"maximum":60,"description":"Minutes of inactivity before scaling to zero"}}},"example":{"port":3000,"min_instances":1,"max_instances":3,"env_vars":{"NODE_ENV":"production","LOG_LEVEL":"info"}}}}},"responses":{"200":{"description":"Updated app","content":{"application/json":{"schema":{"$ref":"#/components/schemas/App"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Apps"],"summary":"Delete app","description":"Soft-delete an app. The app is marked as deleted and permanent cleanup happens asynchronously according to platform retention policies.","operationId":"deleteApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppAction"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/deploy/image":{"post":{"tags":["Apps"],"summary":"Deploy from image","description":"Deploy the app from a container image. The deployment is asynchronous — poll GET /v1/apps/{app_id} for status updates.\n\n**Supported registries include:** `registry.wayscloud.services/`, `ghcr.io/`, `docker.io/`, and Docker Hub shorthand (e.g. `nginx:latest`, `traefik/whoami`).","operationId":"deployAppImage","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["image_uri"],"properties":{"image_uri":{"type":"string","minLength":5,"maxLength":500,"description":"Container image URI"}}},"example":{"image_uri":"ghcr.io/acme-corp/invoice-api:v2.4.1"}}}},"responses":{"200":{"description":"Deployment initiated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppDeployResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid image URI or unsupported registry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"No host available in the requested region","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/start":{"post":{"tags":["Apps"],"summary":"Start app","description":"Start a stopped app or wake it from scale-to-zero state.","operationId":"startApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App starting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppAction"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/stop":{"post":{"tags":["Apps"],"summary":"Stop app","description":"Stop all running instances. The app can be restarted later.","operationId":"stopApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App stopping","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppAction"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/restart":{"post":{"tags":["Apps"],"summary":"Restart app","description":"Restart the app by recreating running instances.","operationId":"restartApp","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"}],"responses":{"200":{"description":"App restarting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppAction"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/apps/{app_id}/logs":{"get":{"tags":["Apps"],"summary":"Get app logs","description":"Returns the most recent container log output (stdout/stderr). This is live container output, not a historical log archive. Logs may be empty for stopped or idle apps. For persistent logging, configure your app to ship logs to an external service.","operationId":"getAppLogs","security":[{"ServiceAuth":[]}],"parameters":[{"name":"app_id","in":"path","required":true,"schema":{"type":"string"},"description":"App ID"},{"name":"lines","in":"query","schema":{"type":"integer","default":100,"minimum":1,"maximum":1000},"description":"Number of log lines to retrieve"}],"responses":{"200":{"description":"Log output","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppLogs"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"App not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/storage/buckets":{"get":{"tags":["Storage"],"summary":"List buckets","description":"Get all storage buckets with name, creation date, and usage statistics.","operationId":"listBuckets","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"Buckets","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageBucketList"}}}}}},"post":{"tags":["Storage"],"summary":"Create bucket","description":"Create a new bucket. Names: 3-63 chars, lowercase/numbers/hyphens, globally unique, cannot be reused after deletion.\n\n**Storage Tiers:**\n- `standard` (default): Reliable storage for general use\n- `enterprise`: High-performance storage with dedicated infrastructure and enhanced durability","operationId":"createBucket","security":[{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bucket_name"],"properties":{"bucket_name":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$","minLength":3,"maxLength":63,"description":"Unique bucket name (lowercase, numbers, hyphens)","example":"my-app-uploads"},"tier":{"type":"string","default":"standard","enum":["standard","enterprise"],"description":"Storage tier: standard (general use) or enterprise (dedicated infrastructure, enhanced performance)"},"region":{"type":"string","default":"no-oslo-1","description":"Storage region (currently only no-oslo-1 available)","example":"no-oslo-1"}}},"example":{"bucket_name":"my-app-uploads","tier":"standard"}}}},"responses":{"201":{"description":"Bucket created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageBucketCreated"}}}},"400":{"description":"Invalid bucket name format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid bucket name. Must be 3-63 chars, lowercase, numbers, and hyphens only."}}}},"409":{"description":"Bucket name conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Bucket name already taken or was previously used. Choose a different name."}}}}}}},"/v1/storage/buckets/{bucket_name}":{"get":{"tags":["Storage"],"summary":"Get bucket","description":"Get bucket details: name, creation date, object count, and storage used.","operationId":"getBucket","security":[{"ServiceAuth":[]}],"parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string"},"description":"Bucket name"}],"responses":{"200":{"description":"Bucket details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorageBucket"}}}},"404":{"description":"Bucket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Bucket not found"}}}}}},"delete":{"tags":["Storage"],"summary":"Delete bucket","description":"Delete a bucket permanently. Must be empty first. Deleted names cannot be reused.","operationId":"deleteBucket","security":[{"ServiceAuth":[]}],"parameters":[{"name":"bucket_name","in":"path","required":true,"schema":{"type":"string"},"description":"Name of the bucket to delete"}],"responses":{"200":{"description":"Bucket deleted permanently","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"404":{"description":"Bucket not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Bucket not found"}}}},"409":{"description":"Bucket is not empty","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Bucket is not empty. Delete all objects first."}}}}}}},"/v1/storage/credentials":{"get":{"tags":["Storage"],"summary":"Get S3 credentials","description":"Get Access Key and Secret Key for S3-compatible access. Endpoint: `storage.wayscloud.services`, Region: `eu-north-1`.","operationId":"getStorageCredentials","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"S3 credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/S3Credentials"}}}}}}},"/v1/dns/zones":{"get":{"tags":["DNS"],"summary":"List zones","description":"Get all DNS zones with nameservers and record counts.","operationId":"listDnsZones","security":[{"ServiceAuth":[]}],"responses":{"200":{"description":"DNS zones","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSZoneList"}}}}}},"post":{"tags":["DNS"],"summary":"Create zone","description":"Create a DNS zone for a domain. Update your registrar nameservers after creation.","operationId":"createDnsZone","security":[{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","description":"Domain name","example":"example.com"}},"example":{"domain":"example.com"}}}}},"responses":{"201":{"description":"Zone created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSZone"}}}},"400":{"description":"Invalid domain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid domain format"}}}}}}},"/v1/dns/zones/{zone_name}":{"get":{"tags":["DNS"],"summary":"Get zone","description":"Get zone details: nameservers, DNSSEC status, and record count.","operationId":"getDnsZone","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name","example":"example.com"}],"responses":{"200":{"description":"Zone details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSZone"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}},"delete":{"tags":["DNS"],"summary":"Delete zone","description":"Delete a zone and all its records permanently. Irreversible.","operationId":"deleteDnsZone","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name"}],"responses":{"200":{"description":"Zone deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dns/zones/{zone_name}/records":{"get":{"tags":["DNS"],"summary":"List records","description":"Get all DNS records (A, AAAA, CNAME, MX, TXT, etc.) with values and TTLs.","operationId":"listDnsRecords","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name"}],"responses":{"200":{"description":"DNS records","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSRecordList"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}},"post":{"tags":["DNS"],"summary":"Create record","description":"Add a DNS record. Supported types: A, AAAA, CNAME, MX, TXT, SRV, NS, CAA. Propagates within 5 minutes.","operationId":"createDnsRecord","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain name"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["record_type","host","record"],"properties":{"record_type":{"type":"string","enum":["A","AAAA","CNAME","MX","TXT","SRV","NS","CAA","PTR"],"example":"A"},"host":{"type":"string","description":"Hostname","example":"www"},"record":{"type":"string","description":"Value","example":"192.0.2.1"},"ttl":{"type":"integer","default":3600,"example":3600},"priority":{"type":"integer","description":"For MX/SRV","example":10}},"example":{"record_type":"A","host":"www","record":"192.0.2.1","ttl":3600}}}}},"responses":{"201":{"description":"Record created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSRecord"}}}},"400":{"description":"Invalid format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid format"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dns/zones/{zone_name}/records/{record_id}":{"put":{"tags":["DNS"],"summary":"Update record","description":"Update record value, TTL, or priority.","operationId":"updateDnsRecord","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"},{"name":"record_id","in":"path","required":true,"schema":{"type":"string"},"description":"Record ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"host":{"type":"string","example":"www"},"record":{"type":"string","example":"192.0.2.50"},"ttl":{"type":"integer","example":1800},"priority":{"type":"integer","example":10}},"example":{"record":"192.0.2.50","ttl":1800}}}}},"responses":{"200":{"description":"Record updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSRecord"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}}},"delete":{"tags":["DNS"],"summary":"Delete record","description":"Delete a DNS record from a zone.","operationId":"deleteDnsRecord","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"},{"name":"record_id","in":"path","required":true,"schema":{"type":"string"},"description":"Record ID"}],"responses":{"200":{"description":"Record deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}}}},"/v1/dns/zones/{zone_name}/dnssec/activate":{"post":{"tags":["DNS"],"summary":"Activate DNSSEC","description":"Enable DNSSEC to protect against DNS spoofing. Configure returned DS records at your registrar.","operationId":"activateDnssec","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"}],"responses":{"200":{"description":"DNSSEC activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSSECInfo"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dns/zones/{zone_name}/dnssec":{"delete":{"tags":["DNS"],"summary":"Deactivate DNSSEC","description":"Disable DNSSEC. Remove DS records from registrar first to avoid resolution failures.","operationId":"deactivateDnssec","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"}],"responses":{"200":{"description":"DNSSEC deactivated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/dns/zones/{zone_name}/statistics":{"get":{"tags":["DNS"],"summary":"Get statistics","description":"Get query statistics: counts, response times, and geographic distribution.","operationId":"getDnsStatistics","security":[{"ServiceAuth":[]}],"parameters":[{"name":"zone_name","in":"path","required":true,"schema":{"type":"string"},"description":"Domain"}],"responses":{"200":{"description":"Zone statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSStatistics"}}}},"404":{"description":"Zone not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Zone not found"}}}}}}},"/v1/regions":{"get":{"tags":["Regions"],"summary":"List regions","description":"Get available regions with service availability (storage, database, VPS, LLM).","operationId":"listRegions","parameters":[{"name":"service","in":"query","schema":{"type":"string","enum":["storage","database","llm","vps"]},"description":"Filter by service"},{"name":"status","in":"query","schema":{"type":"string","enum":["active","maintenance","deprecated"]},"description":"Filter by status"}],"responses":{"200":{"description":"Regions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionList"}}}}}}},"/v1/regions/{region_code}":{"get":{"tags":["Regions"],"summary":"Get region","description":"Get region details: available services, location, and pricing.","operationId":"getRegion","parameters":[{"name":"region_code","in":"path","required":true,"schema":{"type":"string"},"description":"Region code (e.g., no, se, de)"}],"responses":{"200":{"description":"Region details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Region"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}}}},"/v1/regions/{region_code}/pricing":{"get":{"tags":["Regions"],"summary":"Get pricing","description":"Get service prices for a specific region.","operationId":"getRegionPricing","parameters":[{"name":"region_code","in":"path","required":true,"schema":{"type":"string"},"description":"Region code"}],"responses":{"200":{"description":"Service pricing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionPricing"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}}}},"/v1/llm/models":{"get":{"tags":["LLM"],"summary":"List models","description":"List available models (WAYSCloud endpoint)","operationId":"list_llm_models_v1_llm_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIModelList"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/models":{"get":{"tags":["LLM"],"summary":"List models","description":"List available models (OpenAI-compatible endpoint)\n\nReturns a list of all available LLM models.\n\n**Response Example:**\n```json\n{\n  \"object\": \"list\",\n  \"data\": [\n    {\"id\": \"deepseek-v3\", \"object\": \"model\", \"owned_by\": \"wayscloud\"},\n    {\"id\": \"qwen3-235b-instruct\", \"object\": \"model\", \"owned_by\": \"wayscloud\"},\n    {\"id\": \"qwen3-vl-8b\", \"object\": \"model\", \"owned_by\": \"wayscloud\"}\n  ]\n}\n```","operationId":"list_openai_models_v1_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenAIModelList"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/llm/chat":{"post":{"tags":["LLM"],"summary":"Chat completion","description":"WAYSCloud LLM chat completion endpoint\n\nSupports both streaming and non-streaming responses.","operationId":"llm_chat_completion_v1_llm_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ChatResponse"},{"$ref":"#/components/schemas/ErrorResponse"}],"title":"Response Llm Chat Completion V1 Llm Chat Post"},"example":{"id":"chatcmpl-abc123","object":"chat.completion","created":1700000000,"model":"qwen3-235b-thinking","choices":[{"index":0,"message":{"role":"assistant","content":"The capital of Norway is Oslo."},"finish_reason":"stop"}],"usage":{"prompt_tokens":24,"completion_tokens":8,"total_tokens":32}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"error":{"message":"messages is required and must be a non-empty array","type":"invalid_request_error","code":"validation_error"}}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"error":{"message":"messages is required","type":"invalid_request_error","code":"missing_field"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"example":{"error":{"message":"Invalid API key","type":"authentication_error","code":"invalid_api_key"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"error":{"message":"Rate limit exceeded. Retry after 60 seconds.","type":"rate_limit_error","code":"rate_limit_exceeded"}}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/llm/chat/completions":{"post":{"tags":["LLM"],"summary":"Chat completion","description":"WAYSCloud LLM chat completion endpoint (with /completions suffix)\n\nAlias for /v1/llm/chat for nginx compatibility.\nSupports both streaming and non-streaming responses.","operationId":"llm_chat_completions_v1_llm_chat_completions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ChatResponse"},{"$ref":"#/components/schemas/ErrorResponse"}],"title":"Response Llm Chat Completions V1 Llm Chat Completions Post"},"example":{"id":"chatcmpl-abc123","object":"chat.completion","created":1700000000,"model":"qwen3-235b-thinking","choices":[{"index":0,"message":{"role":"assistant","content":"The capital of Norway is Oslo."},"finish_reason":"stop"}],"usage":{"prompt_tokens":24,"completion_tokens":8,"total_tokens":32}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"error":{"message":"messages is required and must be a non-empty array","type":"invalid_request_error","code":"validation_error"}}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"error":{"message":"messages is required","type":"invalid_request_error","code":"missing_field"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"example":{"error":{"message":"Invalid API key","type":"authentication_error","code":"invalid_api_key"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"error":{"message":"Rate limit exceeded. Retry after 60 seconds.","type":"rate_limit_error","code":"rate_limit_exceeded"}}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/chat/completions":{"post":{"tags":["LLM"],"summary":"Chat completion","description":"OpenAI-compatible chat completion endpoint\n\nDrop-in replacement for OpenAI's `/v1/chat/completions` endpoint.\nCompatible with OpenAI SDK and all OpenAI-compatible clients.\n\n**Features:**\n- Non-streaming and streaming (SSE) responses\n- Temperature and max_tokens control\n- Agents framework support (tools, agent_id, tool_choice)\n- Automatic token counting and billing\n\n**Request Example:**\n```json\n{\n  \"model\": \"mixtral-8x7b\",\n  \"messages\": [\n    {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n    {\"role\": \"user\", \"content\": \"Hello!\"}\n  ],\n  \"temperature\": 0.7,\n  \"max_tokens\": 100\n}\n```\n\n**Streaming Example:**\n```json\n{\n  \"model\": \"deepseek-v3\",\n  \"messages\": [{\"role\": \"user\", \"content\": \"Write a story\"}],\n  \"stream\": true,\n  \"max_tokens\": 500\n}\n```\n\n**AI Agent Example:**\n```json\n{\n  \"model\": \"deepseek-v3\",\n  \"messages\": [{\"role\": \"user\", \"content\": \"Help me code\"}],\n  \"agent_id\": \"ephemeral\",\n  \"tools\": [],\n  \"tool_choice\": \"auto\"\n}\n```\n\n**Available Models:**\n\n*Chat — general purpose:*\n- `deepseek-v3`, `deepseek-v3.1` - Versatile, 131k context\n- `qwen3-235b-instruct` - Flagship MoE, 262k context\n- `llama-3.3-70b` - Enterprise, 131k context\n- `mixtral-8x7b` - Fast lightweight MoE\n- `gpt-oss-120b`, `gpt-oss-20b` - Open-weight\n- `gemma-3n-e4b` - Ultra-lightweight, cheapest\n- `kimi-k2.5` - Long-context conversational\n- `qwen3.5-9b` - Lightweight, 262k context\n\n*Reasoning:*\n- `deepseek-r1` - Advanced reasoning\n- `qwen3-235b-thinking` - MoE reasoning\n\n*Code:*\n- `qwen3-coder-480b` - Code generation\n\n*Vision (multimodal):*\n- `qwen3-vl-8b` - Text + image input\n\n*Embeddings:*\n- `embedding-multilingual` - 1024-dim, for RAG\n\n*Moderation:*\n- `llamaguard-4` - Content safety","operationId":"openai_chat_completion_v1_chat_completions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ChatResponse"},{"$ref":"#/components/schemas/ErrorResponse"}],"title":"Response Openai Chat Completion V1 Chat Completions Post"},"example":{"id":"chatcmpl-abc123","object":"chat.completion","created":1700000000,"model":"qwen3-235b-thinking","choices":[{"index":0,"message":{"role":"assistant","content":"The capital of Norway is Oslo."},"finish_reason":"stop"}],"usage":{"prompt_tokens":24,"completion_tokens":8,"total_tokens":32}}}}},"422":{"description":"Validation error","content":{"application/json":{"example":{"error":{"message":"messages is required and must be a non-empty array","type":"invalid_request_error","code":"validation_error"}}}}},"400":{"description":"Invalid request","content":{"application/json":{"example":{"error":{"message":"messages is required","type":"invalid_request_error","code":"missing_field"}}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"example":{"error":{"message":"Invalid API key","type":"authentication_error","code":"invalid_api_key"}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"example":{"error":{"message":"Rate limit exceeded. Retry after 60 seconds.","type":"rate_limit_error","code":"rate_limit_exceeded"}}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/llm/embeddings":{"post":{"tags":["LLM"],"summary":"Create Embedding","description":"OpenAI-compatible embeddings endpoint.\n\nRequest body:\n    {\"model\": \"embedding-multilingual\", \"input\": \"text\" | [\"t1\", \"t2\"]}\n\nResponse: OpenAI-compatible embedding list.","operationId":"create_embedding_v1_llm_embeddings_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/embeddings":{"post":{"tags":["LLM"],"summary":"Create Embedding","description":"OpenAI-compatible embeddings endpoint.\n\nRequest body:\n    {\"model\": \"embedding-multilingual\", \"input\": \"text\" | [\"t1\", \"t2\"]}\n\nResponse: OpenAI-compatible embedding list.","operationId":"create_embedding_v1_embeddings_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"ServiceAuth":[]}]}},"/storage/s3":{"get":{"tags":["Storage"],"summary":"S3 protocol guide","description":"WAYSCloud Storage is fully AWS S3 compatible. Use any AWS SDK or S3 client.\n\n**Endpoint:** `https://storage.wayscloud.services` | **Region:** `eu-north-1`\n\n**AWS CLI**\n\n```bash\n# Configure\naws configure set default.s3.endpoint_url https://storage.wayscloud.services\n\n# List buckets\naws s3 ls\n\n# Upload file\naws s3 cp local.txt s3://your-bucket/remote.txt\n\n# Download file\naws s3 cp s3://your-bucket/remote.txt local.txt\n```\n\n**Python (boto3)**\n\n```python\nimport boto3\n\ns3 = boto3.client('s3',\n    endpoint_url='https://storage.wayscloud.services',\n    aws_access_key_id='YOUR_ACCESS_KEY',\n    aws_secret_access_key='YOUR_SECRET_KEY',\n    region_name='eu-north-1'\n)\n\n# List objects\nresponse = s3.list_objects_v2(Bucket='your-bucket')\nfor obj in response.get('Contents', []):\n    print(obj['Key'])\n\n# Upload file\ns3.upload_file('local.txt', 'your-bucket', 'remote.txt')\n\n# Download file\ns3.download_file('your-bucket', 'remote.txt', 'local.txt')\n```\n\n**JavaScript (AWS SDK v3)**\n\n```javascript\nimport { S3Client, ListObjectsV2Command } from \"@aws-sdk/client-s3\";\n\nconst s3 = new S3Client({\n  endpoint: \"https://storage.wayscloud.services\",\n  region: \"eu-north-1\",\n  credentials: {\n    accessKeyId: \"YOUR_ACCESS_KEY\",\n    secretAccessKey: \"YOUR_SECRET_KEY\"\n  }\n});\n\nconst response = await s3.send(new ListObjectsV2Command({\n  Bucket: \"your-bucket\"\n}));\n```\n\nGet your Access Key and Secret Key from `/v1/storage/credentials` or your [dashboard](https://my.wayscloud.services).\n","operationId":"storageS3Info","responses":{"200":{"description":"S3 protocol documentation"}}}},"/v1/domain-verification/domains":{"post":{"tags":["Domain Verification"],"summary":"Register domain","description":"Register a domain for verification. Returns DNS setup instructions.\n\nAfter registration, add the DNS record to your domain to prove ownership. Then call the verify endpoint to check.\n\n**Purposes:**\n- `email` - For sending email from this domain\n- `custom_host` - For custom hostnames/CNAMEs\n- `webhook` - For webhook endpoint verification\n- `link_branding` - For branded tracking links\n- `verify_channel` - For communication channel verification\n- `other` - General domain ownership verification\n","operationId":"registerDomain","security":[{"AccountAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDomainRequest"}}}},"responses":{"200":{"description":"Domain registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerification"}}}},"400":{"description":"Invalid domain or already registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid domain format or domain already registered"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token (requires scope: domain_verification:write)"}}}}}},"get":{"tags":["Domain Verification"],"summary":"List domains","description":"List all domain verifications for your account. Supports filtering by purpose and status.","operationId":"listDomains","security":[{"AccountAuth":[]}],"parameters":[{"name":"purpose","in":"query","schema":{"type":"string","enum":["email","custom_host","webhook","link_branding","verify_channel","other"]},"description":"Filter by purpose"},{"name":"status","in":"query","schema":{"type":"string","enum":["pending","verified","failed","revoked"]},"description":"Filter by status"},{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1},"description":"Page number"},{"name":"page_size","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100},"description":"Items per page"}],"responses":{"200":{"description":"Domain list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerificationList"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token (requires scope: domain_verification:read)"}}}}}}},"/v1/domain-verification/domains/{domain_id}":{"get":{"tags":["Domain Verification"],"summary":"Get domain details","description":"Get details for a specific domain verification including DNS instructions.","operationId":"getDomain","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"responses":{"200":{"description":"Domain details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerification"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}},"patch":{"tags":["Domain Verification"],"summary":"Revoke domain","description":"Revoke a verified domain. Only status change to `revoked` is allowed.","operationId":"revokeDomain","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["revoked"]}}},"example":{"status":"revoked"}}}},"responses":{"200":{"description":"Domain revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainVerification"}}}},"400":{"description":"Invalid status change","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid status change for this domain"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}},"delete":{"tags":["Domain Verification"],"summary":"Delete domain","description":"Delete a domain verification. This is a soft delete - records are preserved for audit.","operationId":"deleteDomain","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"responses":{"200":{"description":"Domain deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSuccess"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}}},"/v1/domain-verification/domains/{domain_id}/verify":{"post":{"tags":["Domain Verification"],"summary":"Verify domain","description":"Trigger a DNS verification check for the domain.\n\nPerforms DNS lookup to verify that the required record exists. Returns the verification result immediately.\n\n**DNS record must be in place before calling this endpoint.**\n","operationId":"verifyDomain","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyDomainResponse"}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}}},"/v1/domain-verification/domains/{domain_id}/history":{"get":{"tags":["Domain Verification"],"summary":"Get verification history","description":"Get the verification history for a domain including all check attempts and status changes.","operationId":"getDomainHistory","security":[{"AccountAuth":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Domain verification ID"}],"responses":{"200":{"description":"Verification history","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DomainVerificationHistory"},"example":[{"id":"c3d4e5f6-a7b8-9012-cdef-345678901234","event_type":"check_completed","previous_status":"pending","new_status":"verified","dns_response":{"records_found":true,"actual_values":["wayscloud-domain-verification=abc123xyz789"]},"performed_by":"api","created_at":"2025-12-08T10:30:00Z"}]}}}},"401":{"description":"Invalid or missing PAT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing PAT token"}}}},"404":{"description":"Domain not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Domain not found"}}}}}}},"/v1/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts","description":"List all contacts with optional filtering by type, search query, or tags.","operationId":"listContacts","security":[{"AccountAuth":["contacts:read"]}],"parameters":[{"name":"search","in":"query","schema":{"type":"string"},"description":"Search by name, email, or phone"},{"name":"type","in":"query","schema":{"type":"string","enum":["person","company"]},"description":"Filter by contact type"},{"name":"tags","in":"query","schema":{"type":"string"},"description":"Filter by tag (comma-separated for multiple)"},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"per_page","in":"query","schema":{"type":"integer","default":50,"maximum":100}}],"responses":{"200":{"description":"Contact list","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total":{"type":"integer"},"page":{"type":"integer"},"per_page":{"type":"integer"}},"example":{"contacts":[{"id":"a1b2c3d4-5678-9abc-def0-123456789abc","first_name":"Ola","last_name":"Nordmann","email":"ola@example.com","phone_number":"+4799999999","contact_type":"person","tags":["vip"]}],"total":1,"page":1,"per_page":50}}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}}}},"post":{"tags":["Contacts"],"summary":"Create contact","description":"Create a new contact. Phone numbers should be in E.164 format for SMS sending.","operationId":"createContact","security":[{"AccountAuth":["contacts:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"201":{"description":"Contact created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid input. Check required fields."}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}}}}},"/v1/contacts/{contact_id}":{"get":{"tags":["Contacts"],"summary":"Get contact","description":"Get details of a specific contact.","operationId":"getContact","security":[{"AccountAuth":["contacts:read"]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Contact details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Contact not found"}}}}}},"patch":{"tags":["Contacts"],"summary":"Update contact","description":"Update an existing contact. Only provided fields are updated.","operationId":"updateContact","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"200":{"description":"Contact updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Contact not found"}}}}}},"delete":{"tags":["Contacts"],"summary":"Delete contact","description":"Delete a contact. This is a soft delete - the contact can be restored via GDPR endpoints.","operationId":"deleteContact","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Contact deleted"},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Contact not found"}}}}}}},"/v1/contacts/{contact_id}/gdpr-delete":{"post":{"tags":["Contacts"],"summary":"GDPR anonymize contact","description":"Permanently anonymize contact data for GDPR compliance. This is irreversible.","operationId":"gdprDeleteContact","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Reason for GDPR deletion"}}},"example":{"reason":"Customer requested data deletion per GDPR Article 17"}}}},"responses":{"200":{"description":"Contact anonymized"},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Contact not found"}}}}}}},"/v1/contacts/groups":{"get":{"tags":["Contacts"],"summary":"List contact groups","description":"List all contact groups with member counts.","operationId":"listContactGroups","security":[{"AccountAuth":["contacts:read"]}],"responses":{"200":{"description":"Group list","content":{"application/json":{"schema":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/ContactGroup"}}},"example":{"groups":[{"id":"a1b2c3d4-1234-5678-9abc-def012345678","name":"Newsletter Subscribers","member_count":150,"created_at":"2025-01-15T09:30:00Z"}]}}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}}}},"post":{"tags":["Contacts"],"summary":"Create contact group","description":"Create a new contact group for organizing contacts.","operationId":"createContactGroup","security":[{"AccountAuth":["contacts:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroupCreate"}}}},"responses":{"201":{"description":"Group created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid input. Check required fields."}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}}}}},"/v1/contacts/groups/{group_id}":{"get":{"tags":["Contacts"],"summary":"Get contact group","description":"Get details of a specific contact group including member list.","operationId":"getContactGroup","security":[{"AccountAuth":["contacts:read"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Group details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}},"patch":{"tags":["Contacts"],"summary":"Update contact group","description":"Update group name or description.","operationId":"updateContactGroup","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroupCreate"}}}},"responses":{"200":{"description":"Group updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactGroup"}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}},"delete":{"tags":["Contacts"],"summary":"Delete contact group","description":"Delete a contact group. Contacts in the group are not deleted.","operationId":"deleteContactGroup","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Group deleted"},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}}},"/v1/contacts/groups/{group_id}/members":{"get":{"tags":["Contacts"],"summary":"List group members","description":"List all contacts in a group.","operationId":"listGroupMembers","security":[{"AccountAuth":["contacts:read"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Member list","content":{"application/json":{"schema":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}},"example":{"members":[{"id":"b2c3d4e5-6789-0abc-def1-234567890abc","first_name":"Ola","last_name":"Nordmann","phone_number":"+4799999999"}]}}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}},"post":{"tags":["Contacts"],"summary":"Add members to group","description":"Add one or more contacts to a group.","operationId":"addGroupMembers","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact_ids"],"properties":{"contact_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}},"example":{"contact_ids":["a1b2c3d4-5678-9abc-def0-123456789abc","b2c3d4e5-6789-0abc-def1-234567890abc"]}}}},"responses":{"200":{"description":"Members added","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"integer"}},"example":{"added":3}}}}},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group not found"}}}}}}},"/v1/contacts/groups/{group_id}/members/{contact_id}":{"delete":{"tags":["Contacts"],"summary":"Remove member from group","description":"Remove a contact from a group.","operationId":"removeGroupMember","security":[{"AccountAuth":["contacts:write"]}],"parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Member removed"},"401":{"description":"Invalid or missing token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing authentication token"}}}},"404":{"description":"Group or contact not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Group or contact not found"}}}}}}},"/v1/verify/start":{"post":{"tags":["Verify"],"summary":"Start verification","description":"Start a new verification session and send code via SMS, voice call, or email.\n\n## Example Request (SMS)\n```json\n{\n  \"channel\": \"sms\",\n  \"recipient\": \"+4712345678\",\n  \"code_length\": 6,\n  \"ttl_seconds\": 300,\n  \"client_reference\": \"signup-user-42\",\n  \"locale\": \"no\"\n}\n```\n\n## Example Request (Voice)\n```json\n{\n  \"channel\": \"voice\",\n  \"recipient\": \"+4712345678\",\n  \"locale\": \"no\"\n}\n```\n\n## Example Request (Email)\n```json\n{\n  \"channel\": \"email\",\n  \"recipient\": \"user@example.com\",\n  \"locale\": \"no\"\n}\n```\n\n## Parameters\n| Field | Required | Default | Description |\n|-------|----------|---------|-------------|\n| `channel` | No | `sms` | Channel: `sms`, `voice`, or `email` |\n| `recipient` | **Yes** | - | Phone (E.164) for SMS/Voice, email for Email |\n| `code_length` | No | `6` | Code length (4-8 digits) |\n| `ttl_seconds` | No | `300` | Session TTL in seconds (60-900) |\n| `client_reference` | No | - | Your reference for tracking |\n| `locale` | No | `en` | Message language: en, no, nb, sv, da, fi |\n| `metadata` | No | - | Custom metadata object (stored, not processed) |\n| `client_ip` | No | - | End-user IP for risk scoring. If provided, `ip_risk` is included in response |\n\n## Channel Notes\n\n**SMS**: Requires sender name configured in dashboard settings.\n\n**Voice**: Caller ID +47 21 53 50 30. Code read twice. Languages: en, no, sv, da, fi.\n\n**Email**: From-address is your verified domain or `no-reply@wayscloud.net`. Verify domain via Domain Verification API to use custom from-address.\n","operationId":"startVerification","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["recipient"],"properties":{"channel":{"type":"string","enum":["sms","voice","email"],"default":"sms","description":"Verification channel"},"recipient":{"type":"string","description":"Phone (E.164) or email address","example":"+4712345678"},"code_length":{"type":"integer","minimum":4,"maximum":8,"default":6,"description":"Verification code length"},"ttl_seconds":{"type":"integer","minimum":60,"maximum":900,"default":300,"description":"Session TTL in seconds"},"client_reference":{"type":"string","description":"Your tracking reference (e.g., signup-123)"},"locale":{"type":"string","enum":["en","no","nb","sv","da","fi"],"default":"en","description":"Message language"},"metadata":{"type":"object","description":"Custom metadata (stored, not processed)"},"client_ip":{"type":"string","description":"End-user IP for risk scoring. If provided, ip_risk is included in response","example":"203.0.113.42"}}},"examples":{"sms":{"summary":"SMS verification","value":{"channel":"sms","recipient":"+4712345678","locale":"no"}},"voice":{"summary":"Voice verification (TTS call)","value":{"channel":"voice","recipient":"+4712345678","locale":"no"}},"email":{"summary":"Email verification","value":{"channel":"email","recipient":"user@example.com","locale":"no"}}}}}},"responses":{"201":{"description":"Verification started","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","delivered"]},"channel":{"type":"string","enum":["sms","voice","email"]},"expires_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"ip_risk":{"type":"object","nullable":true,"description":"IP risk assessment (only if client_ip provided)","properties":{"score":{"type":"number","description":"Threat score 0-100"},"is_vpn":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"total_reports":{"type":"integer"},"country_code":{"type":"string","nullable":true},"error":{"type":"string","nullable":true,"description":"Error if lookup failed"}}}}},"example":{"session_id":"550e8400-e29b-41d4-a716-446655440000","status":"delivered","channel":"sms","expires_at":"2025-12-12T10:05:00Z","created_at":"2025-12-12T10:00:00Z"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid recipient format. Use E.164 for SMS/voice or email address."}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"503":{"description":"Channel temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Voice channel temporarily unavailable. Try SMS instead."}}}}}}},"/v1/verify/check":{"post":{"tags":["Verify"],"summary":"Check verification code","description":"Verify the code entered by the user.\n\n## Example Request\n```json\n{\n  \"session_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n  \"code\": \"123456\",\n  \"client_ip\": \"203.0.113.42\"\n}\n```\n\nThe `client_ip` is optional. If provided, the response includes `ip_risk` with threat assessment data.\n\n## Error Codes\n| Error | HTTP | Meaning | User Action |\n|-------|------|---------|-------------|\n| `invalid_code` | 200 | Wrong code entered | Show remaining attempts |\n| `session_expired` | 200 | Session TTL exceeded | Start new verification |\n| `max_attempts_exceeded` | 200 | 5 attempts used | Start new verification |\n| `session_not_found` | 404 | Invalid session ID | Check session_id |\n| `voice_no_answer` | 200 | Voice call not answered | Try SMS or retry |\n| `voice_busy` | 200 | Phone line busy | Retry later |\n\nSessions are automatically locked after 5 failed attempts.\n","operationId":"checkVerification","security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["session_id","code"],"properties":{"session_id":{"type":"string","format":"uuid","description":"Session ID from /start response"},"code":{"type":"string","minLength":4,"maxLength":8,"description":"Verification code entered by user"},"client_ip":{"type":"string","description":"End-user IP for risk scoring","example":"203.0.113.42"}}},"example":{"session_id":"550e8400-e29b-41d4-a716-446655440000","code":"123456"}}}},"responses":{"200":{"description":"Verification result","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["verified","pending","failed","expired"]},"verified_at":{"type":"string","format":"date-time"},"error":{"type":"string","description":"Error code if failed"},"attempts_remaining":{"type":"integer","description":"Remaining attempts"},"ip_risk":{"type":"object","nullable":true,"description":"IP risk assessment (only if client_ip provided)","properties":{"score":{"type":"number","description":"Threat score 0-100"},"is_vpn":{"type":"boolean"},"is_proxy":{"type":"boolean"},"is_tor":{"type":"boolean"},"is_datacenter":{"type":"boolean"},"total_reports":{"type":"integer"},"country_code":{"type":"string","nullable":true},"error":{"type":"string","nullable":true}}}}},"examples":{"success":{"summary":"Successful verification","value":{"status":"verified","verified_at":"2025-12-12T10:01:23Z"}},"invalid_code":{"summary":"Wrong code","value":{"status":"pending","error":"invalid_code","attempts_remaining":4}},"expired":{"summary":"Session expired","value":{"status":"expired","error":"session_expired"}},"max_attempts":{"summary":"Too many attempts","value":{"status":"failed","error":"max_attempts_exceeded","attempts_remaining":0}}}}}},"404":{"description":"Session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Session not found"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/verify/status/{session_id}":{"get":{"tags":["Verify"],"summary":"Get session status","description":"Get current status of a verification session.\n\n## Response Fields\n| Field | Description |\n|-------|-------------|\n| `session_id` | Unique session identifier |\n| `channel` | Channel used: `sms` or `voice` |\n| `status` | Current status |\n| `recipient` | Full phone number |\n| `attempt_count` | Number of /check attempts made |\n| `max_attempts` | Maximum allowed attempts (5) |\n| `client_reference` | Your tracking reference |\n\n## Status Values\n- `pending` - Session created, delivery in progress\n- `delivered` - Code sent/played to recipient\n- `verified` - Successfully verified ✓\n- `failed` - Max attempts or delivery failed\n- `expired` - Session TTL exceeded\n- `cancelled` - Manually cancelled\n","operationId":"getVerificationStatus","security":[{"BearerAuth":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID from /start response"}],"responses":{"200":{"description":"Session status","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"channel":{"type":"string","enum":["sms","voice","email"]},"status":{"type":"string","enum":["pending","delivered","verified","failed","expired","cancelled"]},"recipient":{"type":"string","example":"+4712345678"},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"delivered_at":{"type":"string","format":"date-time"},"verified_at":{"type":"string","format":"date-time"},"attempt_count":{"type":"integer"},"max_attempts":{"type":"integer","default":5},"client_reference":{"type":"string"},"error_message":{"type":"string","description":"Error details if failed"}}},"example":{"session_id":"550e8400-e29b-41d4-a716-446655440000","channel":"sms","status":"delivered","recipient":"+4712345678","created_at":"2025-12-12T10:00:00Z","expires_at":"2025-12-12T10:05:00Z","delivered_at":"2025-12-12T10:00:01Z","verified_at":null,"attempt_count":0,"max_attempts":5,"client_reference":"signup-user-42"}}}},"404":{"description":"Session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Session not found"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/verify/sessions/{session_id}":{"delete":{"tags":["Verify"],"summary":"Cancel session","description":"Cancel a pending or delivered verification session. Cannot cancel verified, failed, or expired sessions.","operationId":"cancelVerificationSession","security":[{"BearerAuth":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Session cancelled"},"404":{"description":"Session not found or cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Session not found or already completed"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/gpu/engines":{"get":{"tags":["GPU Studio"],"summary":"List engines","description":"List available GPU engines for image and video generation. No authentication required.","operationId":"listGpuEngines","parameters":[{"name":"type","in":"query","schema":{"type":"string","enum":["image","video"]},"description":"Filter by type"}],"responses":{"200":{"description":"List of engines","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GPUEngineList"}}}}}}},"/v1/gpu/presets":{"get":{"tags":["GPU Studio"],"summary":"List presets","description":"List available style presets. No authentication required.","operationId":"listGpuPresets","parameters":[{"name":"type","in":"query","schema":{"type":"string","enum":["image","video"]},"description":"Filter by type"}],"responses":{"200":{"description":"List of presets","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GPUPreset"},"example":[{"name":"cinematic_light","type":"image","description":"Cinematic lighting style"}]}}}}}}},"/v1/gpu/pricing":{"get":{"tags":["GPU Studio"],"summary":"Get pricing","description":"Returns the current public price list per engine. Availability for your account is determined by GET /v1/gpu/engines. No authentication required.","operationId":"getGpuPricing","parameters":[{"name":"currency","in":"query","schema":{"type":"string","enum":["NOK","EUR","USD","SEK","DKK"],"default":"NOK"},"description":"Currency"}],"responses":{"200":{"description":"Pricing list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GPUPricing"},"example":[{"engine":"image-flux-fast","sku":"gpu_image_flux_fast","type":"image","unit":"image","price_per_unit":2.0,"currency":"NOK","display_name":"FLUX Fast","description":"Quick image generation","tier_required":null}]}}}}}}},"/v1/gpu/jobs":{"post":{"tags":["GPU Studio"],"summary":"Create job","description":"Create an image or video generation job.\n\n**Image inputs:**\n- `prompt` (required): Image description\n- `aspect_ratio`: 1:1, 4:5, 16:9, 9:16 (default: 16:9)\n- `negative_prompt`: What to avoid\n- `seed`: Random seed for reproducibility\n\n**Video inputs:**\n- `prompt` (required): Video description\n- `duration_sec`: Duration in seconds (engine-limited)\n- `negative_prompt`: What to avoid\n- `seed`: Random seed for reproducibility","operationId":"createGpuJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GPUJobCreate"}}}},"responses":{"201":{"description":"Job created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GPUJob"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid engine. See GET /v1/gpu/engines for available options."}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"403":{"description":"Tier access denied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"This engine requires pro tier. Upgrade at my.wayscloud.services"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Rate limit exceeded. Retry after 60 seconds."}}}},"451":{"description":"Content blocked by moderation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Content blocked by safety filters. Revise your prompt."}}}},"502":{"description":"Generation service temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Generation service temporarily unavailable. Retry later."}}}}}},"get":{"tags":["GPU Studio"],"summary":"List jobs","description":"List jobs for your account.","operationId":"listGpuJobs","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["queued","processing","completed","failed","cancelled"]},"description":"Filter by status"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50},"description":"Max results"},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Pagination offset"}],"responses":{"200":{"description":"Job list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GPUJob"},"example":[{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","type":"image","engine":"image-flux-fast","status":"completed","progress":100,"output_files":{"image":"https://storage.wayscloud.services/gpu-studio/tenant/<tenant_id>/jobs/<job_id>/output.png"},"estimated_cost":{"amount":2.0,"currency":"NOK"},"actual_cost":{"amount":2.0,"currency":"NOK"},"error_message":null,"created_at":"2025-01-15T10:30:00Z","completed_at":"2025-01-15T10:30:12Z"}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/gpu/jobs/{job_id}":{"get":{"tags":["GPU Studio"],"summary":"Get job","description":"Get job status and details.","operationId":"getGpuJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"200":{"description":"Job details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GPUJob"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}}}},"delete":{"tags":["GPU Studio"],"summary":"Cancel job","description":"Cancel a job if possible. Only queued or processing jobs can be cancelled.","operationId":"cancelGpuJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"204":{"description":"Job cancelled"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}},"409":{"description":"Job cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job cannot be cancelled (already completed)"}}}}}}},"/v1/transcript/jobs":{"post":{"tags":["Speech Intelligence"],"summary":"Create transcription job","description":"Create a transcription job and receive a presigned upload URL.\n\nThe returned `upload_url` is a presigned S3 PUT URL valid for 1 hour. Upload your file directly to this URL, then call the upload-complete endpoint to start processing.\n\nIf the file is not uploaded within 1 hour, the job expires automatically (status becomes `expired`).\n\n**Content validation:** The server validates the file using ffprobe after upload — the file must contain at least one audio stream. The `content_type` field is metadata only; actual format detection is server-side.","operationId":"createTranscriptJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptJobCreate"}}}},"responses":{"200":{"description":"Job created with upload URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptJobCreateResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Unsupported audio format. Supported: mp3, wav, m4a, ogg, flac"}}}}}},"get":{"tags":["Speech Intelligence"],"summary":"List transcription jobs","description":"List transcription jobs for your account with optional status filter.","operationId":"listTranscriptJobs","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["created","queued","processing","ready","failed","expired"]},"description":"Filter by status"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"description":"Max results"},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"default":0},"description":"Pagination offset"}],"responses":{"200":{"description":"Job list","content":{"application/json":{"schema":{"type":"object","properties":{"jobs":{"type":"array","items":{"$ref":"#/components/schemas/TranscriptJob"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"example":{"jobs":[{"job_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","status":"ready","language":"no","original_filename":"meeting.mp3","audio_duration_sec":1847.3,"created_at":"2026-03-30T10:00:00Z"}],"total":1,"limit":20,"offset":0}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/transcript/jobs/{job_id}":{"get":{"tags":["Speech Intelligence"],"summary":"Get transcription job","description":"Get full job details. When status is `ready`, the response includes transcription segments and available export artifacts with presigned download URLs.","operationId":"getTranscriptJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"200":{"description":"Job details with segments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptJob"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}}}},"delete":{"tags":["Speech Intelligence"],"summary":"Delete transcription job","description":"Delete a transcription job. The job is marked as deleted and S3 artifacts are cleaned up. The job will no longer appear in list results.","operationId":"deleteTranscriptJob","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"200":{"description":"Job deleted","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"job_id":{"type":"string"}},"example":{"status":"deleted","job_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}}}}},"/v1/transcript/jobs/{job_id}/upload-complete":{"post":{"tags":["Speech Intelligence"],"summary":"Confirm upload complete","description":"Confirm that the file has been uploaded to the presigned URL. This moves the job from `created` to `queued` and starts processing.\n\nThis endpoint is idempotent — if the job is already `queued`, `processing`, or `ready`, it returns the current status without re-queuing.\n\nThe server verifies that the file exists in S3 before accepting the confirmation. If the file is not found, returns 422.","operationId":"confirmTranscriptUpload","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"responses":{"200":{"description":"Upload confirmed, processing started"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}},"422":{"description":"Upload not found or job expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Upload not found in storage, or job is in a terminal state"}}}}}}},"/v1/transcript/jobs/{job_id}/export":{"post":{"tags":["Speech Intelligence"],"summary":"Export transcription","description":"Generate an export artifact in the specified format. Returns a presigned download URL.\n\n**Formats:**\n- `txt` — Plain text transcript (auto-generated on completion)\n- `json` — Structured JSON with segments and timestamps (auto-generated on completion)\n- `srt` — SubRip subtitle format (generated on demand)\n- `vtt` — WebVTT subtitle format (generated on demand)\n\n`txt` and `json` are created automatically when a job reaches `ready` status. Calling this endpoint for those formats returns the existing artifact. `srt` and `vtt` are generated on first request and cached for subsequent calls.\n\nThe job must be in `ready` status. Returns 422 if the job has not completed processing.","operationId":"exportTranscript","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptExportRequest"}}}},"responses":{"200":{"description":"Export artifact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TranscriptArtifact"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job not found"}}}},"422":{"description":"Job is not in ready status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Job is not in ready status. Current status: processing"}}}}}}},"/v1/sms/send":{"post":{"tags":["SMS"],"summary":"Send SMS","description":"Send SMS message(s) to one or more recipients.\n\n**Recipients:** Specify at least one of:\n- `to`: List of phone numbers (E.164 format, e.g., +4799999999)\n- `to_contacts`: List of contact UUIDs from your contact directory\n- `to_groups`: List of contact group UUIDs\n\n**Message Length:**\n- GSM-7 (standard): 160 chars = 1 SMS, then 153 chars per part\n- Unicode (emoji, etc.): 70 chars = 1 SMS, then 67 chars per part\n- Max 1600 chars (10 parts)\n\n**Scheduling:**\n- Use `scheduled_at` for future delivery (ISO 8601 format, max 30 days ahead)\n- By default, `scheduled_at` is interpreted as UTC\n- Use `timezone` parameter to specify local time (IANA format, e.g., \"Europe/Oslo\")\n- Scheduled messages can be cancelled using POST `/messages/{id}/cancel`\n\n**Delivery Reports:** Provide `callback_url` (HTTPS) to receive status webhooks.","operationId":"sendSms","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendSMSRequest"}}}},"responses":{"200":{"description":"SMS queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendSMSResponse"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid phone number format. Use E.164 (e.g., +4712345678)"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"402":{"description":"Insufficient balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Insufficient balance. Top up at my.wayscloud.services/billing"}}}},"403":{"description":"SMS service not activated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"SMS service not activated. Activate at my.wayscloud.services"}}}}}}},"/v1/sms/messages":{"get":{"tags":["SMS"],"summary":"List messages","description":"List sent and received SMS messages with optional filters.","operationId":"listSmsMessages","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"direction","in":"query","schema":{"type":"string","enum":["OUTBOUND","INBOUND"]},"description":"Filter by direction"},{"name":"status","in":"query","schema":{"type":"string","enum":["QUEUED","SENT","DELIVERED","FAILED"]},"description":"Filter by status"},{"name":"from_date","in":"query","schema":{"type":"string","format":"date"},"description":"From date (YYYY-MM-DD)"},{"name":"to_date","in":"query","schema":{"type":"string","format":"date"},"description":"To date (YYYY-MM-DD)"},{"name":"page","in":"query","schema":{"type":"integer","default":1},"description":"Page number"},{"name":"page_size","in":"query","schema":{"type":"integer","default":50,"maximum":100},"description":"Items per page"}],"responses":{"200":{"description":"Message list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessageList"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/sms/messages/{message_id}":{"get":{"tags":["SMS"],"summary":"Get message","description":"Get details for a specific SMS message.","operationId":"getSmsMessage","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Message ID"}],"responses":{"200":{"description":"Message details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSMessage"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Message not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Message not found"}}}}}}},"/v1/sms/messages/{message_id}/cancel":{"post":{"tags":["SMS"],"summary":"Cancel scheduled message","description":"Cancel a scheduled or queued SMS message before it is sent.\n\n**Cancellable statuses:**\n- `SCHEDULED` - Message waiting for scheduled time\n- `QUEUED` - Message in send queue\n\nMessages that are already `SENDING`, `SENT`, `DELIVERED`, or `FAILED` cannot be cancelled.","operationId":"cancelSmsMessage","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"message_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Message ID to cancel"}],"responses":{"200":{"description":"Message cancelled successfully","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"},"message_id":{"type":"string"}},"example":{"message":"Message cancelled","message_id":"550e8400-e29b-41d4-a716-446655440000"}}}}},"400":{"description":"Message cannot be cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Message cannot be cancelled. Status: DELIVERED"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Message not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Message not found"}}}}}}},"/v1/sms/sender-profiles":{"get":{"tags":["SMS"],"summary":"List sender profiles","description":"List your configured sender profiles (sender IDs).","operationId":"listSenderProfiles","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Sender profiles","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SenderProfile"},"example":[{"id":"550e8400-e29b-41d4-a716-446655440000","name":"marketing","sender_id":"MyBrand","is_default":false,"allow_reply":false,"is_custom_sender":true,"approval_status":"approved","monthly_price":99.0,"created_at":"2025-01-15T10:00:00Z"}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}},"post":{"tags":["SMS"],"summary":"Create sender profile","description":"Create a new sender profile (sender ID).\n\n**Sender ID Types:**\n- **Phone number**: Your own number or a dedicated number\n- **Alphanumeric**: Brand name (max 11 chars, no ÆØÅ, requires brand_confirmation)\n\nCustom alphanumeric sender IDs require admin approval and may have a monthly fee.","operationId":"createSenderProfile","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSenderProfileRequest"}}}},"responses":{"201":{"description":"Profile created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SenderProfile"}}}},"400":{"description":"Invalid sender ID","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid sender ID. Alphanumeric max 11 chars, must start with letter."}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Profile name already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"A sender profile with this name already exists"}}}}}}},"/v1/sms/sender-profiles/{profile_id}":{"delete":{"tags":["SMS"],"summary":"Delete sender profile","description":"Delete a sender profile. Cannot delete the default profile if it is the only one.","operationId":"deleteSenderProfile","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Profile ID"}],"responses":{"204":{"description":"Profile deleted"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Profile not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Sender profile not found"}}}},"409":{"description":"Cannot delete","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Cannot delete: this is your only sender profile"}}}}}}},"/v1/sms/status":{"get":{"tags":["SMS"],"summary":"Get service status","description":"Get SMS service status including message counts and default sender.","operationId":"getSmsStatus","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Service status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSServiceStatus"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/sms/usage":{"get":{"tags":["SMS"],"summary":"Get SMS usage for billing","description":"Get SMS usage statistics for a billing period.\n\nReturns message counts, SMS units consumed, and cost breakdown.\n\n**Pricing per SMS unit:**\n- NOK: 0.99\n- SEK: 0.99\n- DKK: 0.79\n- EUR: 0.10\n- USD: 0.10\n\nCost is calculated as: `units_used × price_per_unit`","operationId":"getSmsUsage","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"period","in":"query","required":false,"description":"Billing period in YYYY-MM format. Defaults to current month.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}$","example":"2025-12"}}],"responses":{"200":{"description":"Usage statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSUsageResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/sms/stats":{"get":{"tags":["SMS"],"summary":"Get SMS statistics","description":"Get aggregated SMS statistics across multiple time periods.\n\nReturns message counts, units, and delivery rates for:\n- Today\n- This week\n- This month\n- Last 30 days\n\nAlso includes a breakdown by message status (QUEUED, SENT, DELIVERED, FAILED, etc).","operationId":"getSmsStats","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Statistics","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSStatsResponse"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/sms/keywords":{"get":{"tags":["SMS Keywords"],"summary":"List keywords","description":"List your reserved keywords for inbound SMS routing.","operationId":"listSmsKeywords","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"responses":{"200":{"description":"Keywords list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SMSKeyword"},"example":[{"id":"550e8400-e29b-41d4-a716-446655440000","keyword":"SUPPORT","description":"Customer support keyword","is_active":true,"webhook_url":"https://example.com/webhooks/sms","auto_reply_enabled":true,"auto_reply_message":"Thank you for contacting support. We will respond shortly.","messages_received":42,"created_at":"2025-01-15T10:00:00Z"}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}},"post":{"tags":["SMS Keywords"],"summary":"Create keyword","description":"Reserve a keyword for inbound SMS routing.\n\nWhen someone sends an SMS starting with your keyword to a shared number, the message is routed to your webhook.\n\n**Example:** Reserve \"SUPPORT\" → User texts \"SUPPORT help me\" → Your webhook receives the message.","operationId":"createSmsKeyword","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateKeywordRequest"}}}},"responses":{"201":{"description":"Keyword created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeyword"}}}},"400":{"description":"Invalid keyword format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid keyword format. Must be alphanumeric, 2-20 characters."}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Keyword already taken","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Keyword already reserved by another customer"}}}}}}},"/v1/sms/keywords/{keyword_id}":{"get":{"tags":["SMS Keywords"],"summary":"Get keyword","description":"Get details for a specific keyword.","operationId":"getSmsKeyword","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"keyword_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Keyword ID"}],"responses":{"200":{"description":"Keyword details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeyword"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Keyword not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Keyword not found"}}}}}},"patch":{"tags":["SMS Keywords"],"summary":"Update keyword","description":"Update keyword settings (webhook URL, auto-reply, etc.).","operationId":"updateSmsKeyword","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"keyword_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Keyword ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateKeywordRequest"}}}},"responses":{"200":{"description":"Keyword updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SMSKeyword"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Keyword not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Keyword not found"}}}}}},"delete":{"tags":["SMS Keywords"],"summary":"Delete keyword","description":"Release a keyword reservation.","operationId":"deleteSmsKeyword","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"keyword_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Keyword ID"}],"responses":{"204":{"description":"Keyword deleted"},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Keyword not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Keyword not found"}}}}}}},"/v1/sms/keywords/check/{keyword}":{"get":{"tags":["SMS Keywords"],"summary":"Check keyword availability","description":"Check if a keyword is available for reservation.","operationId":"checkSmsKeywordAvailability","security":[{"ServiceAuth":[]},{"ApiKeyAuth":[]}],"parameters":[{"name":"keyword","in":"path","required":true,"schema":{"type":"string"},"description":"Keyword to check"}],"responses":{"200":{"description":"Availability status","content":{"application/json":{"schema":{"type":"object","properties":{"keyword":{"type":"string"},"available":{"type":"boolean"}},"example":{"keyword":"SUPPORT","available":true}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/iot/devices":{"get":{"tags":["IoT Platform"],"summary":"List devices","description":"Retrieve a paginated list of registered IoT devices.","operationId":"list_devices_v1_iot_devices_get","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Page number","default":1,"title":"Page"},"description":"Page number"},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Items per page","default":20,"title":"Page Size"},"description":"Items per page"},{"name":"is_active","in":"query","required":false,"schema":{"anyOf":[{"type":"boolean"},{"type":"null"}],"description":"Filter by active status","title":"Is Active"},"description":"Filter by active status"},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search in device name or ID","title":"Search"},"description":"Search in device name or ID"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeviceList"}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"403":{"description":"API key lacks 'iot' permission"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Register device","description":"Register a new IoT device and generate MQTT credentials. Subject to plan device limits.","operationId":"register_device_v1_iot_devices_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceCreateRequest"}}}},"responses":{"201":{"description":"Device registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDevice"}}}},"403":{"description":"Device limit reached"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}":{"get":{"tags":["IoT Platform"],"summary":"Get device","description":"Retrieve detailed information about a specific device.","operationId":"get_device_v1_iot_devices__device_id__get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","description":"Device identifier","title":"Device Id"},"description":"Device identifier"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDevice"}}}},"404":{"description":"Device not found"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"patch":{"tags":["IoT Platform"],"summary":"Update device","description":"Update device name, description, type, metadata, or active status.","operationId":"update_device_v1_iot_devices__device_id__patch","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","description":"Device identifier","title":"Device Id"},"description":"Device identifier"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDevice"}}}},"404":{"description":"Device not found"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete device","description":"Delete device and revoke MQTT credentials. Cannot be undone.","operationId":"delete_device_v1_iot_devices__device_id__delete","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","description":"Device identifier","title":"Device Id"},"description":"Device identifier"}],"responses":{"204":{"description":"Device deleted"},"404":{"description":"Device not found"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/health":{"get":{"tags":["IoT Platform"],"summary":"Get device health","description":"Health score (0-100) with component breakdown: uptime, alarms, stability, freshness.","operationId":"get_device_health_v1_iot_devices__device_id__health_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","description":"Device identifier","title":"Device Id"},"description":"Device identifier"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeviceHealth"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/tags":{"get":{"tags":["IoT Platform"],"summary":"List device tags","operationId":"list_device_tags_v1_iot_devices__device_id__tags_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeviceTags"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Add tags to device","operationId":"add_device_tags_v1_iot_devices__device_id__tags_post","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"Tags to add","title":"Tags"},"example":["production","floor-3"]}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeviceTagsAdded"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/tags/{tag}":{"delete":{"tags":["IoT Platform"],"summary":"Remove tag from device","operationId":"remove_device_tag_v1_iot_devices__device_id__tags__tag__delete","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"tag","in":"path","required":true,"schema":{"type":"string","description":"Tag to remove","title":"Tag"},"description":"Tag to remove"}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/groups":{"get":{"tags":["IoT Platform"],"summary":"List device groups","operationId":"list_groups_v1_iot_groups_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroupList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create device group","operationId":"create_group_v1_iot_groups_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroup"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/groups/{group_id}":{"get":{"tags":["IoT Platform"],"summary":"Get device group","operationId":"get_group_v1_iot_groups__group_id__get","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroup"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update device group","operationId":"update_group_v1_iot_groups__group_id__put","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroup"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete device group","operationId":"delete_group_v1_iot_groups__group_id__delete","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/groups/{group_id}/devices":{"post":{"tags":["IoT Platform"],"summary":"Add devices to group","operationId":"add_devices_to_group_v1_iot_groups__group_id__devices_post","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"description":"Device IDs to add","title":"Device Ids"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTGroupDevicesAdded"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/groups/{group_id}/devices/{device_id}":{"delete":{"tags":["IoT Platform"],"summary":"Remove device from group","operationId":"remove_device_from_group_v1_iot_groups__group_id__devices__device_id__delete","parameters":[{"name":"group_id","in":"path","required":true,"schema":{"type":"string","title":"Group Id"}},{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/profiles":{"get":{"tags":["IoT Platform"],"summary":"List device profiles","operationId":"list_profiles_v1_iot_profiles_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfileList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create device profile","operationId":"create_profile_v1_iot_profiles_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfile"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/profiles/{profile_id}":{"get":{"tags":["IoT Platform"],"summary":"Get device profile","operationId":"get_profile_v1_iot_profiles__profile_id__get","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfile"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update device profile","operationId":"update_profile_v1_iot_profiles__profile_id__put","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProfileUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfile"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete device profile","operationId":"delete_profile_v1_iot_profiles__profile_id__delete","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/profiles/{profile_id}/apply/{device_id}":{"post":{"tags":["IoT Platform"],"summary":"Apply profile to device","operationId":"apply_profile_to_device_v1_iot_profiles__profile_id__apply__device_id__post","parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}},{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTProfileApplyResult"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/rules":{"get":{"tags":["IoT Platform"],"summary":"List alarm rules","operationId":"list_rules_v1_iot_rules_get","parameters":[{"name":"scope_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by scope type","title":"Scope Type"},"description":"Filter by scope type"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRuleList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create alarm rule","operationId":"create_rule_v1_iot_rules_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/rules/{rule_id}":{"get":{"tags":["IoT Platform"],"summary":"Get alarm rule","operationId":"get_rule_v1_iot_rules__rule_id__get","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update alarm rule","operationId":"update_rule_v1_iot_rules__rule_id__put","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuleUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete alarm rule","operationId":"delete_rule_v1_iot_rules__rule_id__delete","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/rules/{rule_id}/enable":{"post":{"tags":["IoT Platform"],"summary":"Enable alarm rule","operationId":"enable_rule_v1_iot_rules__rule_id__enable_post","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/rules/{rule_id}/disable":{"post":{"tags":["IoT Platform"],"summary":"Disable alarm rule","operationId":"disable_rule_v1_iot_rules__rule_id__disable_post","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTRule"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms":{"get":{"tags":["IoT Platform"],"summary":"List alarms","description":"Retrieve alarms with optional filtering by status, severity, device, or rule.","operationId":"list_alarms_v1_iot_alarms_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: active, acknowledged, resolved, suppressed, expired","title":"Status"},"description":"Filter: active, acknowledged, resolved, suppressed, expired"},{"name":"severity","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: critical, warning, info","title":"Severity"},"description":"Filter: critical, warning, info"},{"name":"device_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by device ID","title":"Device Id"},"description":"Filter by device ID"},{"name":"rule_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by rule ID","title":"Rule Id"},"description":"Filter by rule ID"},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Page Size"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarmList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/summary":{"get":{"tags":["IoT Platform"],"summary":"Get alarm summary","description":"Alarm counts grouped by status and severity.","operationId":"alarm_summary_v1_iot_alarms_summary_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarmSummary"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}":{"get":{"tags":["IoT Platform"],"summary":"Get alarm details","operationId":"get_alarm_v1_iot_alarms__alarm_id__get","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarm"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}/timeline":{"get":{"tags":["IoT Platform"],"summary":"Get alarm event timeline","operationId":"get_alarm_timeline_v1_iot_alarms__alarm_id__timeline_get","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarmTimeline"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}/acknowledge":{"post":{"tags":["IoT Platform"],"summary":"Acknowledge alarm","operationId":"acknowledge_alarm_v1_iot_alarms__alarm_id__acknowledge_post","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlarmActionRequest","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarm"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}/resolve":{"post":{"tags":["IoT Platform"],"summary":"Resolve alarm","operationId":"resolve_alarm_v1_iot_alarms__alarm_id__resolve_post","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlarmActionRequest","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarm"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/alarms/{alarm_id}/reopen":{"post":{"tags":["IoT Platform"],"summary":"Reopen a resolved alarm","operationId":"reopen_alarm_v1_iot_alarms__alarm_id__reopen_post","parameters":[{"name":"alarm_id","in":"path","required":true,"schema":{"type":"string","title":"Alarm Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlarmActionRequest","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTAlarm"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/datapoints":{"get":{"tags":["IoT Platform"],"summary":"List datapoint definitions","operationId":"list_datapoints_v1_iot_datapoints_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDatapointList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create datapoint definition","operationId":"create_datapoint_v1_iot_datapoints_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatapointCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDatapoint"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/datapoints/{datapoint_id}":{"get":{"tags":["IoT Platform"],"summary":"Get datapoint definition","operationId":"get_datapoint_v1_iot_datapoints__datapoint_id__get","parameters":[{"name":"datapoint_id","in":"path","required":true,"schema":{"type":"string","title":"Datapoint Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDatapoint"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update datapoint definition","operationId":"update_datapoint_v1_iot_datapoints__datapoint_id__put","parameters":[{"name":"datapoint_id","in":"path","required":true,"schema":{"type":"string","title":"Datapoint Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DatapointUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDatapoint"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete datapoint definition","operationId":"delete_datapoint_v1_iot_datapoints__datapoint_id__delete","parameters":[{"name":"datapoint_id","in":"path","required":true,"schema":{"type":"string","title":"Datapoint Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/telemetry":{"get":{"tags":["IoT Platform"],"summary":"Get device telemetry","description":"Retrieve telemetry data for a device, optionally filtered by datapoint and time range.","operationId":"get_device_telemetry_v1_iot_devices__device_id__telemetry_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}},{"name":"datapoint_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by datapoint ID","title":"Datapoint Id"},"description":"Filter by datapoint ID"},{"name":"hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"description":"Hours of history to retrieve","default":24,"title":"Hours"},"description":"Hours of history to retrieve"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTTelemetryData"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/devices/{device_id}/telemetry/latest":{"get":{"tags":["IoT Platform"],"summary":"Get latest telemetry values","operationId":"get_device_telemetry_latest_v1_iot_devices__device_id__telemetry_latest_get","parameters":[{"name":"device_id","in":"path","required":true,"schema":{"type":"string","title":"Device Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTTelemetryData"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/channels":{"get":{"tags":["IoT Platform"],"summary":"List notification channels","operationId":"list_notification_channels_v1_iot_notifications_channels_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannelList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create notification channel","operationId":"create_notification_channel_v1_iot_notifications_channels_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannel"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Name conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"A channel with this name already exists"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/channels/{channel_id}":{"get":{"tags":["IoT Platform"],"summary":"Get notification channel","operationId":"get_notification_channel_v1_iot_notifications_channels__channel_id__get","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannel"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update notification channel","operationId":"update_notification_channel_v1_iot_notifications_channels__channel_id__put","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannel"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete notification channel","operationId":"delete_notification_channel_v1_iot_notifications_channels__channel_id__delete","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/channels/{channel_id}/test":{"post":{"tags":["IoT Platform"],"summary":"Send test notification","operationId":"test_notification_channel_v1_iot_notifications_channels__channel_id__test_post","parameters":[{"name":"channel_id","in":"path","required":true,"schema":{"type":"string","title":"Channel Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTChannelTestResult"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/policies":{"get":{"tags":["IoT Platform"],"summary":"List notification policies","operationId":"list_notification_policies_v1_iot_notifications_policies_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTPolicyList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]},"post":{"tags":["IoT Platform"],"summary":"Create notification policy","operationId":"create_notification_policy_v1_iot_notifications_policies_post","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyCreateRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTPolicy"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/policies/{policy_id}":{"get":{"tags":["IoT Platform"],"summary":"Get notification policy","operationId":"get_notification_policy_v1_iot_notifications_policies__policy_id__get","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTPolicy"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"put":{"tags":["IoT Platform"],"summary":"Update notification policy","operationId":"update_notification_policy_v1_iot_notifications_policies__policy_id__put","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTPolicy"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]},"delete":{"tags":["IoT Platform"],"summary":"Delete notification policy","operationId":"delete_notification_policy_v1_iot_notifications_policies__policy_id__delete","parameters":[{"name":"policy_id","in":"path","required":true,"schema":{"type":"string","title":"Policy Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Resource not found"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/notifications/deliveries":{"get":{"tags":["IoT Platform"],"summary":"List notification deliveries","description":"Delivery history with optional filtering by channel type and status.","operationId":"list_notification_deliveries_v1_iot_notifications_deliveries_get","parameters":[{"name":"channel_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by channel type","title":"Channel Type"},"description":"Filter by channel type"},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter: sent, failed, pending, skipped","title":"Status"},"description":"Filter: sent, failed, pending, skipped"},{"name":"created_after","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601 datetime – only return deliveries created after this time","title":"Created After"},"description":"ISO 8601 datetime – only return deliveries created after this time"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTDeliveryList"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/credentials":{"get":{"tags":["IoT Platform"],"summary":"Get MQTT connection info","description":"Returns MQTT broker connection details. Per-device credentials are provided at registration.","operationId":"get_mqtt_credentials_v1_iot_credentials_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTCredentials"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/subscription":{"get":{"tags":["IoT Platform"],"summary":"Get IoT subscription","description":"Returns current plan, device limit, and usage.","operationId":"get_subscription_v1_iot_subscription_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTSubscription"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/usage":{"get":{"tags":["IoT Platform"],"summary":"Get usage statistics","description":"Returns message counts, data usage, and device activity for the current billing period.","operationId":"get_usage_v1_iot_usage_get","parameters":[{"name":"start_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Start date (YYYY-MM-DD)","title":"Start Date"},"description":"Start date (YYYY-MM-DD)"},{"name":"end_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"End date (YYYY-MM-DD)","title":"End Date"},"description":"End date (YYYY-MM-DD)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTUsage"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/iot/stats":{"get":{"tags":["IoT Platform"],"summary":"Get platform statistics","description":"Overview stats: total devices, active, connected, messages, data usage.","operationId":"get_stats_v1_iot_stats_get","parameters":[],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IoTStats"}}}},"422":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"},"example":{"detail":[{"loc":["body","name"],"msg":"Field required","type":"missing"}]}}}},"401":{"description":"Invalid or missing API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}},"security":[{"ServiceAuth":[]}]}},"/v1/chatbot/api/bots":{"get":{"tags":["Chatbot"],"summary":"List chatbots","description":"Returns all active chatbots owned by the authenticated customer. Use the `id` or `slug` from this response as the `bot_id` parameter when calling the chat endpoints.","security":[{"ServiceAuth":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string"},"description":"WAYSCloud API key with `chatbot` permission."}],"responses":{"200":{"description":"List of active chatbots.","content":{"application/json":{"schema":{"type":"object","properties":{"bots":{"type":"array","description":"Active chatbots for this customer.","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique chatbot identifier."},"slug":{"type":"string","description":"URL-friendly identifier. Can be used as `bot_id` in chat requests."},"name":{"type":"string","description":"Display name of the chatbot."},"status":{"type":"string","description":"Current status of the chatbot."},"language":{"type":"string","description":"Primary language (ISO 639-1)."}}}}}},"example":{"bots":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","slug":"support-bot","name":"Customer Support","status":"active","language":"en"}]}}}},"401":{"description":"Missing, invalid, or unauthorized API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/chatbot/api/chat":{"post":{"tags":["Chatbot"],"summary":"Send a message","description":"Send a message to a chatbot and receive a generated response based on the configured knowledge sources. The response includes the answer, relevant source citations, and usage metadata.","security":[{"ServiceAuth":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string"},"description":"WAYSCloud API key with `chatbot` permission."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bot_id","message"],"properties":{"bot_id":{"type":"string","description":"Chatbot identifier. Accepts either the UUID or the slug (e.g. `support-bot`)."},"message":{"type":"string","minLength":1,"maxLength":4000,"description":"The message to send to the chatbot."},"session_id":{"type":"string","nullable":true,"description":"Optional session identifier to continue an existing conversation. If omitted, a new session is created automatically. Reuse the returned `session_id` in subsequent requests to maintain conversation context."}},"example":{"bot_id":"support-bot","message":"What are your prices?"}}}}},"responses":{"200":{"description":"Generated response from the chatbot.","content":{"application/json":{"schema":{"type":"object","required":["session_id","message_id","response"],"properties":{"session_id":{"type":"string","format":"uuid","description":"Session identifier. Reuse this in subsequent requests to continue the same conversation."},"message_id":{"type":"string","format":"uuid","description":"Unique identifier for this response."},"response":{"type":"string","description":"Generated reply from the chatbot based on its knowledge sources."},"citations":{"type":"array","description":"Sources referenced in the response, when available. May be empty if the answer was generated without specific source matches.","items":{"type":"object","properties":{"title":{"type":"string","description":"Title of the source document."},"url":{"type":"string","description":"URL of the source."},"snippet":{"type":"string","description":"Relevant excerpt from the source."}}}},"tokens_input":{"type":"integer","description":"Number of input tokens consumed."},"tokens_output":{"type":"integer","description":"Number of output tokens generated."},"response_time_ms":{"type":"integer","description":"Total processing time in milliseconds."}},"example":{"session_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","message_id":"f1e2d3c4-b5a6-7890-abcd-ef1234567890","response":"Our Standard plan starts at 199 NOK/month and includes 3 chatbots with up to 3,000 messages per month.","citations":[{"title":"Pricing","url":"https://example.com/pricing","snippet":"Standard plan from 199 NOK/month..."}],"tokens_input":245,"tokens_output":87,"response_time_ms":2800}}}}},"401":{"description":"Missing, invalid, or unauthorized API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Chatbot not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Chatbot not found or not accessible"}}}},"429":{"description":"Rate limit or quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Rate limit exceeded. Retry after 60 seconds."}}}}}}},"/v1/chatbot/api/chat/stream":{"post":{"tags":["Chatbot"],"summary":"Send a message (streaming)","description":"Send a message and receive a real-time streaming response via Server-Sent Events (SSE). Tokens are delivered incrementally as they are generated.\n\nThe first event is always `meta`, followed by zero or more `token` events, and finally `done`. If an error occurs during generation, an `error` event is sent instead of `done`.\n\n**Event types:**\n\n| Type | Description |\n|------|-------------|\n| `meta` | Sent once at the start. Contains session ID and source citations. |\n| `token` | Sent for each generated token. |\n| `done` | Sent when generation is complete. |\n| `error` | Sent if an error occurs. Contains a human-readable message. |\n\n**Example stream:**\n```\ndata: {\"type\":\"meta\",\"data\":{\"session_id\":\"a1b2...\",\"citations\":[{\"title\":\"Pricing\",\"url\":\"https://example.com/pricing\"}]}}\ndata: {\"type\":\"token\",\"content\":\"Our\"}\ndata: {\"type\":\"token\",\"content\":\" prices\"}\ndata: {\"type\":\"token\",\"content\":\" start\"}\ndata: {\"type\":\"token\",\"content\":\" from\"}\ndata: {\"type\":\"done\"}\n```\n\n**Error example:**\n```\ndata: {\"type\":\"error\",\"content\":\"Service temporarily unavailable\"}\n```","security":[{"ServiceAuth":[]}],"parameters":[{"name":"X-API-Key","in":"header","required":true,"schema":{"type":"string"},"description":"WAYSCloud API key with `chatbot` permission."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bot_id","message"],"properties":{"bot_id":{"type":"string","description":"Chatbot identifier. Accepts either the UUID or the slug."},"message":{"type":"string","minLength":1,"maxLength":4000,"description":"The message to send to the chatbot."},"session_id":{"type":"string","nullable":true,"description":"Optional session identifier to continue an existing conversation. Reuse the `session_id` from a previous response to maintain context."}},"example":{"bot_id":"support-bot","message":"Tell me about your services"}}}}},"responses":{"200":{"description":"Streaming response via Server-Sent Events. Events are JSON objects prefixed with `data: `.","content":{"text/event-stream":{"schema":{"type":"string","description":"JSON-encoded SSE events in the order: meta → token* → done (or error)."}}}},"401":{"description":"Missing, invalid, or unauthorized API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"404":{"description":"Chatbot not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Chatbot not found or not accessible"}}}},"429":{"description":"Rate limit or quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Rate limit exceeded. Retry after 60 seconds."}}}}}}},"/v1/live-events/watch/{slug}":{"get":{"tags":["Live Events"],"summary":"Resolve watch state","description":"Returns the current watch state for an event. The watch page polls this every 10 seconds to detect state changes (e.g. event goes live, replay becomes available).\n\nReturns `watch_state` (viewer-facing UI state) and `status` (internal lifecycle). Playback objects are only included for public events — auth-required events must call verify-password or verify-token first.\n\nDraft events are not publicly resolvable and return 404.","operationId":"resolveWatchState","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"product-demo"},"description":"Event URL slug from the watch page URL"}],"responses":{"200":{"description":"Event state resolved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchStateResponse"},"examples":{"live":{"summary":"Public event, currently live","value":{"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo","name":"Product Demo","description":"Live demo of new release features","status":"live","watch_state":"live","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":"2026-04-10T12:01:10+00:00","actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null,"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":null}},"not_started":{"summary":"Scheduled event, not yet live","value":{"event_id":"c1f4e8a2-5b3d-4f6a-9c8e-2d7f1a3b5c9e","slug":"spring-webinar","name":"Spring Release Webinar","description":"Quarterly product update for customers","status":"scheduled","watch_state":"not_started","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-15T14:00:00+00:00","scheduled_end_at":"2026-04-15T15:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":true,"playback_mode":"none","live_playback":null,"replay_playback":null,"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":null}},"replay":{"summary":"Ended event with replay","value":{"event_id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo","name":"Product Demo","description":"Live demo of new release features","status":"ended","watch_state":"replay_available","visibility_mode":"public","requires_auth":false,"auth_type":null,"scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":"2026-04-10T12:01:10+00:00","actual_end_at":"2026-04-10T13:28:45+00:00","timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T13:28:45+00:00"},"embed_enabled":true,"banner_url":null,"logo_url":null,"replay_published_at":"2026-04-10T13:35:00+00:00"}},"access_required":{"summary":"Token-protected event","value":{"event_id":"b2d4f6a8-1c3e-5a7b-9d0f-4e6a8c2b1d3f","slug":"board-meeting-q2","name":"Q2 Board Meeting","description":null,"status":"live","watch_state":"access_required","visibility_mode":"token","requires_auth":true,"auth_type":"token","scheduled_start_at":"2026-04-10T09:00:00+00:00","scheduled_end_at":"2026-04-10T11:00:00+00:00","actual_start_at":"2026-04-10T09:02:30+00:00","actual_end_at":null,"timezone":"Europe/Oslo","countdown_enabled":false,"playback_mode":"none","live_playback":null,"replay_playback":null,"embed_enabled":false,"banner_url":null,"logo_url":null,"replay_published_at":null}}}}}},"404":{"description":"Event not found. The slug does not exist, or the event is in draft status and not publicly resolvable.","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/{slug}/verify-password":{"post":{"tags":["Live Events"],"summary":"Verify event password","description":"Verifies the password for a password-protected event. On success, issues a short-lived access grant and returns the current playback information.\n\nFailed attempts are logged for abuse detection.","operationId":"verifyPassword","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"private-webinar"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["password"],"properties":{"password":{"type":"string","minLength":1,"description":"Event password set by the event organizer"}}},"example":{"password":"MyEventPass2026"}}}},"responses":{"200":{"description":"Password correct. Access grant issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAccessResponse"},"examples":{"live_event":{"summary":"Event is currently live","value":{"verified":true,"access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T15:00:00+00:00","watch_state":"live","playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null}},"replay_available":{"summary":"Event ended, replay available","value":{"verified":true,"access_grant":"ag_dGTNLjOFmQh_iaJUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T18:00:00+00:00","watch_state":"replay_available","playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T18:00:00+00:00"}}}}}}},"400":{"description":"Event is not password-protected","content":{"application/json":{"example":{"detail":"Event is not password-protected"}}}},"403":{"description":"Invalid password","content":{"application/json":{"example":{"detail":"Invalid password"}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/{slug}/verify-token":{"post":{"tags":["Live Events"],"summary":"Verify watch token","description":"Validates an access token for a token-gated event. On success, issues a short-lived access grant and returns playback information.\n\nThe token `use_count` is **not** incremented here — it is consumed when playback actually starts via the consume-grant endpoint. This means opening the watch page and entering a token does not use up a token slot.\n\nThe token can also be supplied as a URL parameter on the watch page (`?token=...`) for auto-verification.","operationId":"verifyToken","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"board-meeting-q2"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","minLength":1,"description":"Full watch token value as received when the token was created"}}},"example":{"token":"wayscloud_le_k5q2ubCC8QAx1L2RNNMn8dLQ7bEeVNTm"}}}},"responses":{"200":{"description":"Token valid. Access grant issued. Token **not consumed** yet — call consume-grant when playback starts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyAccessResponse"},"examples":{"live_event":{"summary":"Event is currently live","value":{"verified":true,"access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T15:00:00+00:00","watch_state":"live","playback_mode":"live","live_playback":{"hls_url":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","protocol":"hls"},"replay_playback":null}},"replay_available":{"summary":"Event ended, replay available","value":{"verified":true,"access_grant":"ag_dGTNLjOFmQh_iaJUcXyZk0pQ7wE9vF3hJ6mT8nL","access_grant_expires_at":"2026-04-10T18:00:00+00:00","watch_state":"replay_available","playback_mode":"replay","live_playback":null,"replay_playback":{"url":"https://s3.wayscloud.services/media-events/tenant/c412929e/live-events/9a72d400/sessions/f6e170db/recording.mp4?X-Amz-Expires=604800&X-Amz-Signature=a1b2c3...","protocol":"mp4","duration_seconds":5255,"expires_at":"2026-04-17T18:00:00+00:00"}}}}}}},"400":{"description":"Event is not token-protected","content":{"application/json":{"example":{"detail":"Event is not token-protected"}}}},"403":{"description":"Invalid token, expired, or usage limit reached","content":{"application/json":{"examples":{"invalid":{"summary":"Token not found","value":{"detail":"Invalid token"}},"expired":{"summary":"Token past expiry","value":{"detail":"Token expired"}},"limit":{"summary":"All uses consumed","value":{"detail":"Token usage limit reached"}}}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/watch/{slug}/consume-grant":{"post":{"tags":["Live Events"],"summary":"Consume access grant on playback start","description":"Called by the watch page when video playback actually starts. Marks the access grant as consumed and increments the token `use_count`.\n\nThis is the point where a token use is actually spent. Idempotent — calling twice with the same grant returns `already_consumed: true` without incrementing again.\n\nIf the grant has expired, returns 403.","operationId":"consumeAccessGrant","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","example":"board-meeting-q2"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["access_grant"],"properties":{"access_grant":{"type":"string","description":"Access grant string (prefixed with `ag_`) received from verify-password or verify-token"}}},"example":{"access_grant":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL"}}}},"responses":{"200":{"description":"Grant consumed successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsumeGrantResponse"},"examples":{"consumed":{"summary":"First consumption","value":{"consumed":true,"already_consumed":false}},"idempotent":{"summary":"Already consumed (idempotent)","value":{"consumed":true,"already_consumed":true}}}}}},"403":{"description":"Invalid or expired access grant","content":{"application/json":{"examples":{"invalid":{"summary":"Grant not found","value":{"detail":"Invalid access grant"}},"expired":{"summary":"Grant expired","value":{"detail":"Access grant expired"}}}}}},"404":{"description":"Event not found","content":{"application/json":{"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events":{"get":{"tags":["Live Events"],"summary":"List events","description":"Returns a paginated list of the authenticated customer's events, ordered by creation date descending.","operationId":"listEvents","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["draft","scheduled","ready","rehearsal","live","ending","ended","processing","failed","cancelled"]},"description":"Filter by lifecycle status"},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100},"description":"Max results"},{"name":"offset","in":"query","schema":{"type":"integer","default":0},"description":"Pagination offset"}],"responses":{"200":{"description":"Event list","content":{"application/json":{"example":{"events":[{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"ready","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}],"total":12,"page":1,"page_size":20}}}},"401":{"description":"Missing or invalid authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}},"post":{"tags":["Live Events"],"summary":"Create event","description":"Creates a new live event, generates RTMP stream credentials, and activates the `live_events` service if this is the customer's first event.\n\n**Important:** The `stream_key` is returned only at creation. Store it securely.","operationId":"createEvent","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveEventCreateRequest"},"example":{"name":"Product Demo","description":"Live demo of new release features","scheduled_start_at":"2026-04-10T12:00:00Z","scheduled_end_at":"2026-04-10T13:30:00Z","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"embed_enabled":true}}}},"responses":{"201":{"description":"Event created. Stream key shown once.","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"draft","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00","stream_key":"qz0bKkTt6Vjvpb7_yQxMXXvpBzohJm0MlCL4V-PQrAU"}}}},"401":{"description":"Missing or invalid authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"409":{"description":"Slug conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"An event with this slug already exists"}}}},"429":{"description":"Plan event limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Plan event limit exceeded. Delete old events or upgrade your plan."}}}}}}},"/v1/live-events/events/{event_id}":{"get":{"tags":["Live Events"],"summary":"Get event","description":"Returns full event detail including ingest status, current session, and recording count.","operationId":"getEvent","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Event detail","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"ready","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}},"patch":{"tags":["Live Events"],"summary":"Update event","description":"Update event properties. **Only allowed when status is `draft`, `scheduled`, or `ready`.** Events that are live, ending, or processing cannot be modified.","operationId":"updateEvent","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveEventUpdateRequest"},"example":{"name":"Updated Demo Title","description":"New description","visibility_mode":"password","password":"MyEventPass2026"}}}},"responses":{"200":{"description":"Event updated","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo-a1b2c3","name":"Updated Demo Title","description":"Live demo of new release features","status":"ready","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"password","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}},"409":{"description":"Event state conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event cannot be modified while live or processing"}}}}}},"delete":{"tags":["Live Events"],"summary":"Delete event","description":"Soft-deletes the event. Retained for 30 days before permanent deletion. **Not allowed while `live`, `ending`, or `processing`.**","operationId":"deleteEvent","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"204":{"description":"Event deleted"},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}},"409":{"description":"Event state conflict","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event is currently live or processing"}}}}}}},"/v1/live-events/events/{event_id}/rehearsal/start":{"post":{"tags":["Live Events"],"summary":"Start rehearsal","description":"Transitions event to `rehearsal`. The encoder can connect and stream, but the watch page shows \"in rehearsal\" to viewers.\n\n**Precondition:** Status must be `ready`.","operationId":"startRehearsal","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Rehearsal started","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"rehearsal","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"inactive","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"409":{"description":"Event not ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event must be in ready state to start rehearsal"}}}}}}},"/v1/live-events/events/{event_id}/live/start":{"post":{"tags":["Live Events"],"summary":"Go live","description":"Transitions event to `live`. Stream becomes visible to viewers. Recording starts if `record_enabled`.\n\n**Precondition:** Status must be `ready` or `rehearsal`.","operationId":"startLive","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Event is now live","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"live","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":"2026-04-10T12:01:10+00:00","actual_end_at":null,"timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"active","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"409":{"description":"Event not ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event must be in ready or rehearsal state to go live"}}}}}}},"/v1/live-events/events/{event_id}/live/stop":{"post":{"tags":["Live Events"],"summary":"Stop live","description":"Stops the live event. Disconnects encoder, ends sessions, transitions to `ending`. Recordings are finalized automatically.\n\n**Precondition:** Status must be `live` or `rehearsal`.","operationId":"stopLive","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Event stopping","content":{"application/json":{"example":{"id":"9a72d400-36a0-4fa5-bf7e-6883f262850a","slug":"product-demo-a1b2c3","name":"Product Demo","description":"Live demo of new release features","status":"ending","scheduled_start_at":"2026-04-10T12:00:00+00:00","scheduled_end_at":"2026-04-10T13:30:00+00:00","actual_start_at":null,"actual_end_at":"2026-04-10T13:28:45+00:00","timezone":"Europe/Oslo","visibility_mode":"public","record_enabled":true,"replay_enabled":true,"rehearsal_enabled":true,"auto_start_on_ingest":false,"embed_enabled":true,"max_bitrate_kbps":null,"max_resolution":null,"retention_days":90,"watch_url":"https://live.wayscloud.services/watch/product-demo-a1b2c3","embed_url":"https://live.wayscloud.services/embed/product-demo-a1b2c3","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","stream_status":"disconnected","recording_count":0,"created_at":"2026-04-08T10:00:00+00:00","updated_at":"2026-04-08T10:00:00+00:00"}}}},"409":{"description":"Event not live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event must be live or in rehearsal to stop"}}}}}}},"/v1/live-events/events/{event_id}/stream-key/rotate":{"post":{"tags":["Live Events"],"summary":"Rotate stream key","description":"Generates a new stream key and invalidates the old one immediately. Encoder must be reconfigured.\n\n**Precondition:** Status must NOT be `live`, `ending`, or `processing`.\n\n**Important:** The new `stream_key` is shown only once in this response.","operationId":"rotateStreamKey","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Stream key rotated","content":{"application/json":{"example":{"stream_key":"Nw7pLm2kHxR9sBq_3TcFvYzA8dGjWe1nUoKi5MhXrQY","ingest_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"Nw7pLm2k"}}}},"409":{"description":"Cannot rotate during live","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Cannot rotate stream key while event is live"}}}}}}},"/v1/live-events/events/{event_id}/recordings":{"get":{"tags":["Live Events"],"summary":"List recordings","description":"Returns recordings for the event. Each live session produces one recording when `record_enabled` is true.","operationId":"listRecordings","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Recordings list","content":{"application/json":{"example":{"recordings":[{"id":"rec-7f3c1c2a-4b5d-6e7f-8a9b-0c1d2e3f4a5b","session_id":"f6e170db-0f68-4dfd-9239-1a7806d24fdb","format":"mp4","duration_seconds":5255,"size_bytes":1073741824,"thumbnail_url":"https://s3.wayscloud.services/media-events/tenant/.../thumbnail.jpg?X-Amz-Expires=604800","replay_url":"https://s3.wayscloud.services/media-events/tenant/.../recording.mp4?X-Amz-Expires=604800","processing_status":"completed","created_at":"2026-04-10T13:35:00+00:00"}]}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events/{event_id}/tokens":{"post":{"tags":["Live Events"],"summary":"Create watch token","description":"Creates an access token for token-gated events (`visibility_mode: token`). The `token` value is returned only at creation.\n\n`max_uses` counts actual playback sessions (via access-grant model), not token form submissions.","operationId":"createWatchToken","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LiveEventTokenCreateRequest"},"example":{"label":"VIP Attendees","max_uses":50,"expires_at":"2026-04-10T18:00:00Z"}}}},"responses":{"201":{"description":"Token created. Full token shown once.","content":{"application/json":{"example":{"id":"tok-a1b2c3d4-e5f6-7890-abcd-ef1234567890","token":"wayscloud_le_k5q2ubCC8QAx1L2RNNMn8dLQ7bEeVNTm","token_prefix":"wayscloud_le","label":"VIP Attendees","max_uses":50,"used_count":0,"valid_from":"2026-04-08T10:00:00+00:00","expires_at":"2026-04-10T18:00:00+00:00","revoked_at":null,"created_at":"2026-04-08T10:00:00+00:00"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}},"get":{"tags":["Live Events"],"summary":"List watch tokens","description":"Returns all watch tokens for the event. Token values are masked — only the prefix is shown.","operationId":"listWatchTokens","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Token list","content":{"application/json":{"example":{"tokens":[{"id":"tok-a1b2c3d4-e5f6-7890-abcd-ef1234567890","token_prefix":"wayscloud_le","label":"VIP Attendees","max_uses":50,"used_count":12,"valid_from":"2026-04-08T10:00:00+00:00","expires_at":"2026-04-10T18:00:00+00:00","revoked_at":null,"created_at":"2026-04-08T10:00:00+00:00"}]}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events/{event_id}/stats":{"get":{"tags":["Live Events"],"summary":"Get event statistics","description":"Returns aggregated statistics for the event across all sessions.","operationId":"getEventStats","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Event statistics","content":{"application/json":{"example":{"total_sessions":3,"total_duration_seconds":8520,"peak_concurrent_viewers":312,"total_unique_viewers":845,"total_recordings":2,"total_recording_size_bytes":2147483648}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/limits":{"get":{"tags":["Live Events"],"summary":"Get plan limits","description":"Returns the customer's current plan capabilities and usage for Live Events.","operationId":"getPlanLimits","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"responses":{"200":{"description":"Plan capabilities","content":{"application/json":{"example":{"plan":"Live Events Pro","max_events_per_month":20,"events_used_this_month":3,"max_concurrent_events":2,"max_duration_minutes":480,"max_bitrate_kbps":8000,"max_resolution":"1080p","recording_enabled":true,"replay_enabled":true,"adaptive_bitrate_enabled":true,"custom_domains_enabled":false,"simulcast_enabled":false,"transcript_enabled":false,"retention_days_default":90,"visibility_modes_allowed":["public","token","password","tenant_only"]}}}},"401":{"description":"Missing or invalid authentication","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}}}}},"/v1/live-events/events/{event_id}/ingest":{"get":{"tags":["Live Events"],"summary":"Get ingest details","description":"Returns ingest configuration for the event. Use this to configure your encoder (OBS, vMix, ffmpeg, etc.).","operationId":"getIngestDetails","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Ingest configuration","content":{"application/json":{"example":{"protocol":"rtmp","rtmp_url":"rtmp://ingest.wayscloud.services/live","stream_key_prefix":"qz0bKkTt","encoder_connected":false,"last_seen_at":null,"stream_status":"inactive"}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events/{event_id}/health":{"get":{"tags":["Live Events"],"summary":"Get event health","description":"Returns operator-friendly health signals for a live event. Data is **best-effort** — derived from periodic media server polling and may be slightly delayed.\n\nDo not use for billing or SLA calculations.","operationId":"getEventHealth","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Health signals","content":{"application/json":{"examples":{"healthy":{"summary":"Healthy live event","value":{"encoder_connected":true,"audio_detected":true,"video_detected":true,"current_resolution":"1920x1080","current_bitrate_kbps":4500,"recording_active":true,"session_status":"active","last_media_heartbeat_at":"2026-04-10T12:15:30+00:00","degraded":false,"operator_message":null,"viewers":47}},"degraded":{"summary":"Degraded — low bitrate","value":{"encoder_connected":true,"audio_detected":true,"video_detected":true,"current_resolution":"1280x720","current_bitrate_kbps":380,"recording_active":true,"session_status":"active","last_media_heartbeat_at":"2026-04-10T12:15:30+00:00","degraded":true,"operator_message":"Source bitrate unusually low","viewers":12}}}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/live-events/events/{event_id}/sessions":{"get":{"tags":["Live Events"],"summary":"List sessions","description":"Returns the history of live sessions for the event. Each time an encoder connects and goes live, a new session is created.","operationId":"listSessions","security":[{"BearerAuth":[]},{"ServiceAuth":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a"},"description":"Event UUID"}],"responses":{"200":{"description":"Session history","content":{"application/json":{"example":{"sessions":[{"id":"f6e170db-0f68-4dfd-9239-1a7806d24fdb","session_number":1,"status":"ended","started_at":"2026-04-10T12:01:10+00:00","ended_at":"2026-04-10T13:28:45+00:00","ended_reason":"operator_stop","source_resolution":"1920x1080","source_bitrate_kbps":4500,"source_codec_video":"h264","source_codec_audio":"aac","peak_viewers":312,"recording_id":"rec-7f3c1c2a-4b5d-6e7f-8a9b-0c1d2e3f4a5b"}]}}}},"404":{"description":"Event not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Event not found"}}}}}}},"/v1/ip/{ip}":{"get":{"tags":["IP Intelligence"],"summary":"IP summary","description":"Returns geolocation, network identity, threat assessment, and detection flags for an IP address. Free tier auto-provisioned on first use.","operationId":"getIpSummary","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string"},"example":"8.8.8.8"}],"responses":{"200":{"description":"Complete IP intelligence summary","content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string","description":"Queried IP address"},"ip_version":{"type":"integer","enum":[4,6],"description":"IP protocol version"},"hostname":{"type":"string","nullable":true,"description":"Reverse DNS hostname (null if no rDNS record)"},"geo":{"type":"object","description":"Geolocation data","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code"},"country_name":{"type":"string","description":"Human-readable country name"},"city":{"type":"string","nullable":true,"description":"City name"},"region":{"type":"string","nullable":true,"description":"Region or state"},"latitude":{"type":"number","nullable":true},"longitude":{"type":"number","nullable":true},"timezone":{"type":"string","nullable":true,"description":"IANA timezone"}}},"network":{"type":"object","description":"Network identity","properties":{"asn":{"type":"integer","nullable":true,"description":"Autonomous System Number"},"isp":{"type":"string","nullable":true,"description":"Internet Service Provider"},"org":{"type":"string","nullable":true,"description":"Organization name"},"connection_type":{"type":"string","enum":["residential","mobile","business","datacenter","hosting","education","government","unknown"],"description":"Connection classification"}}},"threat":{"type":"object","description":"Threat assessment","properties":{"score":{"type":"number","minimum":0,"maximum":100,"description":"Threat score (0 = clean, 100 = critical)"},"level":{"type":"string","enum":["clean","low","medium","high","critical"],"description":"Human-readable threat level"},"is_clean":{"type":"boolean","description":"True if no known threat reports"}}},"flags":{"type":"object","description":"Detection flags","properties":{"vpn":{"type":"boolean","description":"Detected as VPN endpoint"},"proxy":{"type":"boolean","description":"Detected as proxy"},"tor":{"type":"boolean","description":"Detected as Tor exit node"},"datacenter":{"type":"boolean","description":"Hosted in datacenter or cloud"},"botnet":{"type":"boolean","description":"Associated with botnet activity"}}}},"example":{"ip":"8.8.8.8","ip_version":4,"hostname":"dns.google","geo":{"country":"US","country_name":"United States","city":"Mountain View","region":"California","latitude":37.386,"longitude":-122.0838,"timezone":"America/Los_Angeles"},"network":{"asn":15169,"isp":"Google LLC","org":"Google LLC","connection_type":"datacenter"},"threat":{"score":0,"level":"clean","is_clean":true},"flags":{"vpn":false,"proxy":false,"tor":false,"datacenter":true,"botnet":false}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/{ip}/geo":{"get":{"tags":["IP Intelligence"],"summary":"IP geolocation","description":"Returns geolocation, reverse DNS, and ASN information.","operationId":"getIpGeo","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string"},"example":"8.8.8.8"}],"responses":{"200":{"description":"Geolocation, reverse DNS, and network identity","content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string","description":"Queried IP address"},"ip_version":{"type":"integer","description":"IP protocol version (4 or 6)"},"hostname":{"type":"string","nullable":true,"description":"Reverse DNS hostname (null if no rDNS)"},"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code"},"country_name":{"type":"string","description":"Human-readable country name"},"city":{"type":"string","nullable":true,"description":"City name"},"region":{"type":"string","nullable":true,"description":"Region or state"},"latitude":{"type":"number","nullable":true,"description":"Latitude coordinate"},"longitude":{"type":"number","nullable":true,"description":"Longitude coordinate"},"timezone":{"type":"string","nullable":true,"description":"IANA timezone identifier"},"asn":{"type":"integer","nullable":true,"description":"Autonomous System Number"},"isp":{"type":"string","nullable":true,"description":"Internet Service Provider"},"org":{"type":"string","nullable":true,"description":"Organization name"},"connection_type":{"type":"string","enum":["residential","mobile","business","datacenter","hosting","education","government","unknown"],"description":"Connection classification"}},"example":{"ip":"8.8.8.8","ip_version":4,"hostname":"dns.google","country":"US","country_name":"United States","city":"Mountain View","region":"California","latitude":37.386,"longitude":-122.0838,"timezone":"America/Los_Angeles","asn":15169,"isp":"Google LLC","org":"Google LLC","connection_type":"datacenter"}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/{ip}/threat":{"get":{"tags":["IP Intelligence"],"summary":"IP threat assessment","description":"Returns threat score, level, categories, and detection flags.","operationId":"getIpThreat","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"ip","in":"path","required":true,"schema":{"type":"string"},"example":"1.2.3.4"}],"responses":{"200":{"description":"Threat score, categories, timeline, and detection flags","content":{"application/json":{"schema":{"type":"object","properties":{"ip":{"type":"string","description":"Queried IP address"},"score":{"type":"number","minimum":0,"maximum":100,"description":"Threat score (0 = clean, 100 = critical)"},"level":{"type":"string","enum":["clean","low","medium","high","critical"],"description":"Human-readable threat level derived from score"},"is_clean":{"type":"boolean","description":"True if no known threat reports"},"total_reports":{"type":"integer","description":"Number of abuse reports from all sources"},"categories":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Abuse categories (e.g. brute-force, port-scan, spam). Null if clean"},"first_seen":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp of first abuse report"},"last_seen":{"type":"string","format":"date-time","nullable":true,"description":"ISO 8601 timestamp of most recent report"},"flags":{"type":"object","description":"Detection flags","properties":{"vpn":{"type":"boolean","description":"VPN endpoint"},"proxy":{"type":"boolean","description":"Proxy server"},"tor":{"type":"boolean","description":"Tor exit node"},"datacenter":{"type":"boolean","description":"Datacenter/cloud hosted"}}}},"example":{"ip":"203.0.113.50","score":75,"level":"high","is_clean":false,"total_reports":42,"categories":["brute-force","port-scan"],"first_seen":"2025-11-01T08:00:00Z","last_seen":"2026-03-29T14:22:00Z","flags":{"vpn":false,"proxy":true,"tor":false,"datacenter":true}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/threats/live":{"get":{"tags":["IP Intelligence"],"summary":"Live threat feed","description":"Returns the most recently reported threat IPs.","operationId":"getLiveThreats","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":25,"minimum":1,"maximum":100}}],"responses":{"200":{"description":"Recently reported threat IPs sorted by recency","content":{"application/json":{"schema":{"type":"object","properties":{"updated_at":{"type":"string","format":"date-time","description":"ISO 8601 timestamp of feed generation"},"count":{"type":"integer","description":"Number of threat entries returned"},"threats":{"type":"array","description":"List of recently reported threat IPs","items":{"type":"object","properties":{"ip":{"type":"string","description":"Reported IP address"},"score":{"type":"number","description":"Threat score (0-100)"},"level":{"type":"string","description":"Threat level"},"categories":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Abuse categories"},"country":{"type":"string","nullable":true,"description":"ISO country code"},"last_seen":{"type":"string","nullable":true,"description":"Last report timestamp"}}}}},"example":{"updated_at":"2026-03-30T08:00:00Z","count":3,"threats":[{"ip":"203.0.113.50","score":85,"level":"high","categories":["brute-force"],"country":"CN","last_seen":"2026-03-30T07:55:00Z"},{"ip":"198.51.100.22","score":60,"level":"medium","categories":["port-scan"],"country":"RU","last_seen":"2026-03-30T07:50:00Z"},{"ip":"192.0.2.99","score":45,"level":"medium","categories":["spam"],"country":"BR","last_seen":"2026-03-30T07:48:00Z"}]}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/countries/{code}":{"get":{"tags":["IP Intelligence"],"summary":"Country intelligence","description":"Returns threat intelligence summary for a country.","operationId":"getCountryIntelligence","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string"},"example":"NO","description":"ISO 3166-1 alpha-2 country code"}],"responses":{"200":{"description":"Threat intelligence aggregated by country","content":{"application/json":{"schema":{"type":"object","properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code"},"country_name":{"type":"string","nullable":true,"description":"Human-readable country name"},"total_threats":{"type":"integer","description":"Total abuse reports originating from this country"},"threat_density":{"type":"number","nullable":true,"description":"Reports per unique IP (higher = more concentrated abuse)"},"top_categories":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Most common abuse categories from this country"}},"example":{"country":"NO","country_name":"Norway","total_threats":127,"threat_density":0.003,"top_categories":["brute-force","port-scan","spam"]}}}}},"404":{"description":"No data for country","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"No threat data available for this country"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/asn/{asn}":{"get":{"tags":["IP Intelligence"],"summary":"ASN intelligence","description":"Returns threat intelligence summary for an Autonomous System.","operationId":"getAsnIntelligence","security":[{"ApiKeyAuth":[]}],"parameters":[{"name":"asn","in":"path","required":true,"schema":{"type":"integer"},"example":15169,"description":"Autonomous System Number"}],"responses":{"200":{"description":"Threat intelligence aggregated by Autonomous System","content":{"application/json":{"schema":{"type":"object","properties":{"asn":{"type":"integer","description":"Autonomous System Number"},"name":{"type":"string","nullable":true,"description":"AS name (e.g. Google LLC)"},"total_ips":{"type":"integer","nullable":true,"description":"Unique IPs observed in this AS"},"threat_count":{"type":"integer","description":"Total abuse reports from this AS"},"threat_ratio":{"type":"number","nullable":true,"description":"Reports per unique IP (abuse concentration)"},"country":{"type":"string","nullable":true,"description":"Top country by report count"}},"example":{"asn":15169,"name":"Google LLC","total_ips":4200000,"threat_count":89,"threat_ratio":2e-05,"country":"US"}}}}},"404":{"description":"No data for ASN","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"No threat data available for this ASN"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/report":{"post":{"tags":["IP Intelligence"],"summary":"Submit IP abuse report","description":"Report an abusive IP address to the WAYSCloud threat intelligence network.\n\nRequires prior reporter registration via `POST /v1/ip/reporters/register`. Your WAYSCloud API key is used for authentication — the upstream reporter token is managed transparently.\n\n**Use cases:** fail2ban integration, honeypot data, IDS alerts, manual incident reports.\n\n**Rate limiting:** Reports count against your daily quota. Duplicate reports (same IP + category within 1 hour) are deduplicated.","operationId":"submitIpReport","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ip","category"],"properties":{"ip":{"type":"string","description":"IPv4 or IPv6 address to report as abusive","example":"157.180.31.156"},"category":{"type":"string","description":"Abuse category. Use descriptive names — categories are automatically normalized. Common values: ssh_bruteforce, port_scan, http_flood, sip_attack, rdp_bruteforce, web_attack, spam, phishing","example":"ssh_bruteforce"},"severity":{"type":"string","enum":["low","medium","high","critical"],"default":"medium","description":"Threat severity level. Affects how quickly the IP is flagged in the threat intelligence network"},"confidence":{"type":"number","minimum":0,"maximum":1,"default":1.0,"description":"How confident you are in this report (0.0 = uncertain, 1.0 = verified). Lower confidence reduces the impact on threat scoring"},"comment":{"type":"string","nullable":true,"description":"Free-text context about the abuse (e.g. \"Repeated SSH login attempts from this IP over 24h\")"},"session_id":{"type":"string","nullable":true,"description":"Unique session identifier for deduplication across retries. Format: tool-component-timestamp-unique (e.g. fail2ban-ssh-20260330-157180)"}},"example":{"ip":"45.155.205.233","category":"ssh_bruteforce","severity":"high","confidence":0.9,"comment":"Repeated SSH login attempts over 24h","session_id":"fail2ban-ssh-20260330-45155205233"}}}}},"responses":{"200":{"description":"Report accepted and submitted to the threat intelligence network","content":{"application/json":{"schema":{"type":"object","properties":{"report_id":{"type":"integer","description":"Unique numeric report identifier"},"oid_record":{"type":"string","description":"Norwegian NKOM Object Identifier for this report record"},"ip":{"type":"string","description":"The reported IP address"},"category":{"type":"string","description":"Normalized abuse category"},"initial_score":{"type":"number","nullable":true,"description":"Initial threat score assigned to the IP based on this report"},"message":{"type":"string","description":"Human-readable status message"}},"example":{"report_id":12345,"oid_record":"2.16.578.1.62.report.20260330.157180311156","ip":"157.180.31.156","category":"ssh_bruteforce","initial_score":24.0,"message":"Abuse report submitted successfully"}}}}},"403":{"description":"No reporter registration found. Register first via POST /v1/ip/reporters/register","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/reporters/register":{"post":{"tags":["IP Intelligence"],"summary":"Register as abuse reporter","description":"Register your service or organization as an abuse reporter in the WAYSCloud threat intelligence network.\n\nAfter registration, use `POST /v1/ip/report` to submit abuse reports using your regular WAYSCloud API key.\n\n**Trust scores** affect how much weight your reports carry:\n- `automated` (0.4): Fully automated systems like fail2ban or IDS\n- `hybrid` (0.6): Automated detection with human verification\n- `manual` (0.8): Human-verified reports\n- Verified domain (1.0): After DNS TXT verification via `POST /v1/ip/reporters/verify`\n\nEach WAYSCloud customer can have one active reporter registration.","operationId":"registerReporter","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Name of your service or organization (e.g. \"ACME Mail Server\", \"Security Research Lab\")","example":"My Honeypot"},"source_type":{"type":"string","enum":["automated","manual","hybrid"],"default":"automated","description":"Reporter type — determines initial trust score. automated=fail2ban/IDS, manual=human review, hybrid=automated+human"},"contact":{"type":"string","nullable":true,"description":"Contact email for verification and support communication"},"intent":{"type":"string","nullable":true,"description":"What you plan to report (e.g. \"fail2ban SSH bruteforce\", \"honeypot data\", \"manual security analysis\")"},"domain":{"type":"string","nullable":true,"description":"Your domain for ownership verification. Add DNS TXT record later to increase trust score to 1.0"}},"example":{"name":"ACME Security Lab","source_type":"hybrid","contact":"security@acme.com","intent":"fail2ban + manual incident review","domain":"acme.com"}}}}},"responses":{"201":{"description":"Reporter successfully registered","content":{"application/json":{"schema":{"type":"object","properties":{"reporter_id":{"type":"string","description":"Unique reporter UUID — used for domain verification"},"reporter_token":{"type":"string","description":"Upstream reporter token (managed internally — you authenticate with your WAYSCloud API key)"},"oid":{"type":"string","nullable":true,"description":"Norwegian NKOM Object Identifier for this reporter"},"trust_score":{"type":"number","description":"Initial trust score (0.4 automated, 0.6 hybrid, 0.8 manual)"},"status":{"type":"string","description":"Reporter status (active)"},"usage":{"type":"object","description":"Instructions for submitting reports"}},"example":{"reporter_id":"550e8400-e29b-41d4-a716-446655440000","reporter_token":"wayscloud_abc123...","oid":"2.16.578.1.62.reporter.550e8400","trust_score":0.4,"status":"active","usage":{"report_endpoint":"POST /v1/ip/report","auth":"Use your WAYSCloud API key (same X-API-Key header)"}}}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/reporters/verify":{"post":{"tags":["IP Intelligence"],"summary":"Verify reporter domain ownership","description":"Verify that you own the domain associated with your reporter registration. This increases your trust score to 1.0 (maximum), meaning your reports carry the highest weight.\n\n**Steps:**\n1. Register with a domain via `POST /v1/ip/reporters/register`\n2. Add a DNS TXT record: `wayscloud-verify=<reporter_id>` to your domain (or `_wayscloud.yourdomain.com`)\n3. Call this endpoint to trigger verification\n\n**DNS propagation** may take 5–15 minutes after adding the TXT record.","operationId":"verifyReporter","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reporter_id":{"type":"string","nullable":true,"description":"Reporter UUID to verify (from registration response)"},"domain":{"type":"string","nullable":true,"description":"Domain to verify (alternative to reporter_id — looks up the reporter by domain)"}},"example":{"reporter_id":"550e8400-e29b-41d4-a716-446655440000","domain":"acme.com"}}}}},"responses":{"200":{"description":"Domain ownership verified — trust score upgraded to 1.0","content":{"application/json":{"schema":{"type":"object","properties":{"reporter_id":{"type":"string","description":"Verified reporter UUID"},"domain":{"type":"string","description":"Verified domain"},"trust_score":{"type":"number","description":"Updated trust score (1.0 after verification)"},"message":{"type":"string","description":"Verification status message"}},"example":{"reporter_id":"550e8400-e29b-41d4-a716-446655440000","domain":"acme.com","trust_score":1.0,"message":"Domain ownership verified successfully"}}}}},"400":{"description":"DNS TXT record not found — add wayscloud-verify=<reporter_id> to your domain","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Reporter not found or domain not registered","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}},"/v1/ip/delist":{"post":{"tags":["IP Intelligence"],"summary":"Request IP delisting","description":"Request removal or review of an IP address from WAYSCloud threat lists.\n\n**Dual verification required:**\n1. The request should ideally originate from the IP being delisted (IP verification)\n2. You must provide a hostname (e.g. mail.example.com) that resolves to the IP\n3. After submitting, add a DNS TXT record: `wayscloud-delist=<request_id>` on the hostname\n\n**Common use cases:**\n- Server was compromised but is now secured\n- Inherited a \"dirty\" IP from previous owner\n- False positive / misclassification\n- Shared hosting or VPN service\n\n**Timeline:** Requests are reviewed within 24–48 hours after DNS verification.","operationId":"requestDelist","security":[{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ip_address","hostname","reason","contact_email"],"properties":{"ip_address":{"type":"string","description":"IPv4 or IPv6 address to request delisting for","example":"157.180.31.156"},"hostname":{"type":"string","description":"Hostname that resolves to this IP (e.g. mail.example.com). Used for DNS-based ownership verification","example":"mail.example.com"},"reason":{"type":"string","minLength":20,"description":"Detailed reason for the delist request (minimum 20 characters). Explain what happened and what remediation was done","example":"Server was compromised via outdated WordPress plugin. Now fully patched, firewall updated, and malware removed."},"contact_email":{"type":"string","format":"email","description":"Email address for follow-up communication about the delist request","example":"admin@example.com"}},"example":{"ip_address":"157.180.31.156","hostname":"mail.example.com","reason":"Server was compromised via outdated WordPress plugin. Now fully patched, firewall updated, and malware removed.","contact_email":"admin@example.com"}}}}},"responses":{"201":{"description":"Delist request submitted — follow DNS verification instructions in response","content":{"application/json":{"schema":{"type":"object","properties":{"request_id":{"type":"string","description":"Unique request UUID — use this in your DNS TXT record for verification"},"ip_address":{"type":"string","description":"The IP address in the delist request"},"hostname":{"type":"string","description":"The hostname provided for DNS verification"},"verification_status":{"type":"string","nullable":true,"description":"Current verification state (ip_verified, dns_pending, fully_verified)"},"dns_verification":{"type":"object","nullable":true,"description":"DNS TXT record instructions for completing verification"},"message":{"type":"string","description":"Next steps for completing the delist process"}},"example":{"request_id":"550e8400-e29b-41d4-a716-446655440000","ip_address":"157.180.31.156","hostname":"mail.example.com","verification_status":"ip_verified","dns_verification":{"txt_record":"wayscloud-delist=550e8400-e29b-41d4-a716-446655440000","instructions":["Add TXT record to mail.example.com","Test with: dig TXT mail.example.com +short"]},"message":"Delist request submitted. Add DNS TXT record to complete verification."}}}}},"400":{"description":"Invalid request — missing hostname or invalid format","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"IP mismatch — request must originate from the target IP address","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid or missing API key"}}}},"422":{"description":"Invalid input (bad IP format, country code, or ASN)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Invalid IP address format"}}}},"429":{"description":"Daily quota exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"detail":"Daily quota exceeded. Upgrade plan or retry tomorrow."}}}}}}}},"components":{"schemas":{"GPUEngine":{"type":"object","properties":{"engine":{"type":"string","description":"Engine identifier"},"name":{"type":"string","description":"Display name"},"description":{"type":"string"},"type":{"type":"string","enum":["image","video"]},"tier_required":{"type":"string","nullable":true,"description":"Required tier (null = basic)"},"available":{"type":"boolean","description":"Currently available"}},"example":{"engine":"image-flux-pro","name":"FLUX Pro","description":"High-quality images with fine details","type":"image","tier_required":"pro","available":true}},"GPUEngineList":{"type":"array","items":{"$ref":"#/components/schemas/GPUEngine"},"example":[{"engine":"image-flux-fast","name":"FLUX Fast","description":"Quick image drafts","type":"image","tier_required":null,"available":true},{"engine":"image-flux-pro","name":"FLUX Pro","description":"High-quality images","type":"image","tier_required":"pro","available":true},{"engine":"video-veo-pro","name":"Veo Pro","description":"8-sec 1080p video","type":"video","tier_required":"pro","available":true}]},"GPUPreset":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["image","video"]},"description":{"type":"string","nullable":true}},"example":{"name":"cinematic_light","type":"image","description":"Cinematic lighting style"}},"GPUPricing":{"type":"object","properties":{"engine":{"type":"string"},"sku":{"type":"string"},"type":{"type":"string","enum":["image","video"]},"unit":{"type":"string","enum":["image","seconds"]},"price_per_unit":{"type":"number"},"currency":{"type":"string"},"display_name":{"type":"string"},"description":{"type":"string","nullable":true},"tier_required":{"type":"string","nullable":true}},"example":{"engine":"image-flux-fast","sku":"gpu_image_flux_fast","type":"image","unit":"image","price_per_unit":2.0,"currency":"NOK","display_name":"FLUX Fast","description":"Quick image generation","tier_required":null}},"GPUJobCreate":{"type":"object","required":["type","engine","inputs"],"properties":{"type":{"type":"string","enum":["image","video"]},"engine":{"type":"string","description":"Engine to use"},"inputs":{"type":"object","properties":{"prompt":{"type":"string","description":"Description of what to generate"},"aspect_ratio":{"type":"string","enum":["1:1","4:5","16:9","9:16"],"default":"16:9"},"duration_sec":{"type":"integer","description":"Video duration (video only)"},"negative_prompt":{"type":"string","description":"What to avoid"},"seed":{"type":"integer","description":"Random seed"}},"required":["prompt"]},"preset":{"type":"string","nullable":true,"description":"Style preset"},"webhook_url":{"type":"string","format":"uri","nullable":true,"description":"Callback URL (video only)"}},"example":{"type":"image","engine":"image-flux-fast","inputs":{"prompt":"A serene mountain landscape at sunset","aspect_ratio":"16:9"}}},"GPUJobCost":{"type":"object","properties":{"amount":{"type":"number"},"currency":{"type":"string"}},"example":{"amount":2.0,"currency":"NOK"}},"GPUJob":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["image","video"]},"engine":{"type":"string"},"status":{"type":"string","enum":["queued","processing","completed","failed","cancelled"]},"progress":{"type":"integer","minimum":0,"maximum":100},"output_files":{"type":"object","nullable":true,"description":"URLs to generated files"},"estimated_cost":{"$ref":"#/components/schemas/GPUJobCost","nullable":true},"actual_cost":{"$ref":"#/components/schemas/GPUJobCost","nullable":true},"error_message":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","type":"image","engine":"image-flux-fast","status":"completed","progress":100,"output_files":{"image":"https://storage.wayscloud.services/gpu-studio/tenant/<tenant_id>/jobs/<job_id>/output.png"},"estimated_cost":{"amount":2.0,"currency":"NOK"},"actual_cost":{"amount":2.0,"currency":"NOK"},"error_message":null,"created_at":"2025-01-15T10:30:00Z","completed_at":"2025-01-15T10:30:12Z"}},"TranscriptJobCreate":{"type":"object","required":["filename","content_type","file_size_bytes"],"properties":{"filename":{"type":"string","maxLength":255,"description":"Original filename"},"content_type":{"type":"string","maxLength":100,"description":"MIME type (e.g., audio/mpeg, audio/wav)"},"file_size_bytes":{"type":"integer","minimum":1,"maximum":2147483648,"description":"File size in bytes (max 2 GB)"},"language":{"type":"string","maxLength":10,"default":"auto","description":"ISO 639-1 language code or \"auto\" for detection"}},"example":{"filename":"meeting-recording.mp3","content_type":"audio/mpeg","file_size_bytes":15728640,"language":"auto"}},"TranscriptJobCreateResponse":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"upload_url":{"type":"string","format":"uri","description":"Presigned S3 upload URL"},"upload_expires_in":{"type":"integer","description":"Upload URL expiry in seconds"}},"example":{"job_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","upload_url":"https://storage.wayscloud.services/transcript-originals/...","upload_expires_in":3600}},"TranscriptSegment":{"type":"object","properties":{"segment_index":{"type":"integer"},"start_time":{"type":"number","description":"Start time in seconds"},"end_time":{"type":"number","description":"End time in seconds"},"text":{"type":"string"},"confidence":{"type":"number","minimum":0,"maximum":1,"nullable":true}}},"TranscriptArtifact":{"type":"object","properties":{"artifact_id":{"type":"string","format":"uuid"},"format":{"type":"string","enum":["txt","json","srt","vtt"]},"download_url":{"type":"string","format":"uri"},"size_bytes":{"type":"integer"}},"example":{"artifact_id":"c3d4e5f6-7890-1234-abcd-ef5678901234","format":"srt","download_url":"https://api.wayscloud.services/v1/transcript/artifacts/c3d4e5f6-7890-1234-abcd-ef5678901234/download","size_bytes":4820}},"TranscriptJob":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["created","queued","processing","ready","failed","expired"]},"language":{"type":"string","nullable":true,"description":"Detected or specified language code"},"original_filename":{"type":"string"},"audio_duration_sec":{"type":"number","nullable":true,"description":"Audio duration in seconds (available after processing)"},"processing_time_ms":{"type":"integer","nullable":true,"description":"Total processing time in milliseconds"},"error_message":{"type":"string","nullable":true,"description":"Error details (only when status is failed)"},"segments":{"type":"array","items":{"$ref":"#/components/schemas/TranscriptSegment"},"nullable":true,"description":"Transcription segments (only included when status is ready, on detail endpoint)"},"artifacts":{"type":"array","items":{"$ref":"#/components/schemas/TranscriptArtifact"},"nullable":true,"description":"Available export artifacts with download URLs (only included when status is ready, on detail endpoint)"},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}},"example":{"job_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","status":"ready","language":"no","original_filename":"meeting-recording.mp3","audio_duration_sec":1847.3,"processing_time_ms":42150,"error_message":null,"created_at":"2025-01-15T10:30:00Z","completed_at":"2025-01-15T10:30:42Z"}},"TranscriptExportRequest":{"type":"object","required":["format"],"properties":{"format":{"type":"string","enum":["txt","json","srt","vtt"],"description":"Export format"}},"example":{"format":"srt"}},"SendSMSRequest":{"type":"object","required":["message"],"properties":{"to":{"type":"array","items":{"type":"string"},"maxItems":1000,"description":"List of recipient phone numbers (E.164 format)"},"to_contacts":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":1000,"description":"List of contact UUIDs to send to"},"to_groups":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100,"description":"List of contact group UUIDs to send to"},"message":{"type":"string","minLength":1,"maxLength":1600,"description":"Message content"},"sender_profile_id":{"type":"string","description":"Sender profile ID (uses default if not specified)"},"allow_reply":{"type":"boolean","default":false,"description":"Allow recipient to reply"},"customer_ref":{"type":"string","maxLength":255,"description":"Your reference ID for correlation"},"callback_url":{"type":"string","format":"uri","description":"Webhook URL for delivery status (HTTPS required)"},"scheduled_at":{"type":"string","format":"date-time","description":"Schedule for future delivery (ISO 8601). Max 30 days ahead. Use timezone param for local time."},"timezone":{"type":"string","description":"IANA timezone for scheduled_at (e.g., Europe/Oslo, Europe/Stockholm). If omitted, scheduled_at is treated as UTC.","example":"Europe/Oslo"}},"example":{"to":["+4799999999"],"message":"Reminder: Your appointment is tomorrow at 10:00","scheduled_at":"2025-12-17T09:00:00","timezone":"Europe/Oslo","customer_ref":"reminder-12345"}},"SendSMSResponse":{"type":"object","properties":{"sms_message_id":{"type":"string","format":"uuid","description":"Unique message ID"},"to":{"type":"array","items":{"type":"string"},"description":"Recipient numbers"},"status":{"type":"string","enum":["QUEUED","SCHEDULED","SENDING","SENT","DELIVERED","FAILED"],"description":"Message status"},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"units_estimate":{"type":"integer","description":"Estimated SMS units"},"customer_ref":{"type":"string","nullable":true}},"example":{"sms_message_id":"550e8400-e29b-41d4-a716-446655440000","to":["+4799999999"],"status":"QUEUED","units_estimate":1,"customer_ref":"order-12345"}},"SMSMessage":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"direction":{"type":"string","enum":["OUTBOUND","INBOUND"]},"message_type":{"type":"string","enum":["PLAIN","REPLY","VERIFY"]},"from_number":{"type":"string","nullable":true},"to_number":{"type":"string"},"body":{"type":"string"},"status":{"type":"string","enum":["QUEUED","SCHEDULED","SENDING","SENT","DELIVERED","FAILED","CANCELLED","RECEIVED"]},"units_count":{"type":"integer","nullable":true},"customer_ref":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"scheduled_at":{"type":"string","format":"date-time","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"delivered_at":{"type":"string","format":"date-time","nullable":true},"related_message_id":{"type":"string","format":"uuid","nullable":true},"error_message":{"type":"string","nullable":true}},"example":{"id":"550e8400-e29b-41d4-a716-446655440000","direction":"OUTBOUND","message_type":"PLAIN","from_number":null,"to_number":"+4799999999","body":"Your order #1234 has shipped.","status":"DELIVERED","units_count":1,"customer_ref":"order-1234","created_at":"2026-03-30T10:00:00Z","sent_at":"2026-03-30T10:00:01Z","delivered_at":"2026-03-30T10:00:03Z","error_message":null}},"SMSMessageList":{"type":"object","properties":{"messages":{"type":"array","items":{"$ref":"#/components/schemas/SMSMessage"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"},"has_more":{"type":"boolean"}},"example":{"messages":[{"id":"550e8400-e29b-41d4-a716-446655440000","direction":"OUTBOUND","to_number":"+4799999999","body":"Your order #1234 has shipped.","status":"DELIVERED","units_count":1,"created_at":"2026-03-30T10:00:00Z"},{"id":"660f9511-f3a0-5483-b827-1f13c4d55111","direction":"OUTBOUND","to_number":"+4798765432","body":"Meeting at 14:00 today.","status":"SENT","units_count":1,"created_at":"2026-03-30T09:30:00Z"}],"total":47,"page":1,"page_size":50,"has_more":false}},"SenderProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"sender_id":{"type":"string","description":"Phone number or alphanumeric (max 11 chars)"},"is_default":{"type":"boolean"},"allow_reply":{"type":"boolean"},"is_custom_sender":{"type":"boolean"},"approval_status":{"type":"string","enum":["pending","approved","rejected"]},"monthly_price":{"type":"number","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"marketing","sender_id":"MyBrand","is_default":false,"allow_reply":false,"is_custom_sender":true,"approval_status":"approved","monthly_price":99.0,"created_at":"2025-01-15T10:00:00Z"}},"CreateSenderProfileRequest":{"type":"object","required":["name","sender_id"],"properties":{"name":{"type":"string","minLength":1,"maxLength":50,"description":"Profile name"},"sender_id":{"type":"string","minLength":1,"maxLength":15,"description":"Sender ID (phone or alphanumeric max 11 chars, no ÆØÅ)"},"allow_reply":{"type":"boolean","default":true},"is_default":{"type":"boolean","default":false},"brand_confirmation":{"type":"boolean","default":false,"description":"Required for custom sender IDs"}},"example":{"name":"marketing","sender_id":"MyBrand","allow_reply":false,"brand_confirmation":true}},"SMSServiceStatus":{"type":"object","properties":{"active":{"type":"boolean"},"has_sender_profiles":{"type":"boolean"},"has_api_key":{"type":"boolean"},"default_sender":{"$ref":"#/components/schemas/SenderProfile","nullable":true},"messages_today":{"type":"integer"},"messages_this_month":{"type":"integer"},"messages_sent_30d":{"type":"integer"},"messages_received_30d":{"type":"integer"}},"example":{"active":true,"has_sender_profiles":true,"has_api_key":true,"default_sender":{"id":"550e8400-e29b-41d4-a716-446655440000","name":"default","sender_id":"4799999999","is_default":true},"messages_today":12,"messages_this_month":347,"messages_sent_30d":892,"messages_received_30d":45}},"SMSUsageResponse":{"type":"object","description":"SMS usage statistics for a billing period","properties":{"period":{"type":"string","description":"Billing period (YYYY-MM)","example":"2025-12"},"period_start":{"type":"string","format":"date","example":"2025-12-01"},"period_end":{"type":"string","format":"date","example":"2025-12-31"},"messages_sent":{"type":"integer","description":"Total outbound messages","example":150},"messages_received":{"type":"integer","description":"Total inbound messages","example":23},"messages_failed":{"type":"integer","description":"Failed messages","example":2},"units_used":{"type":"integer","description":"SMS units consumed","example":167},"cost":{"type":"number","format":"float","description":"Total cost","example":165.33},"currency":{"type":"string","description":"Currency code","example":"NOK"},"price_per_unit":{"type":"number","format":"float","description":"Price per SMS unit","example":0.99},"outbound_cost":{"type":"number","format":"float","description":"Cost for outbound messages","example":165.33},"inbound_cost":{"type":"number","format":"float","description":"Cost for inbound messages (usually 0)","example":0.0}},"example":{"period":"2025-12","period_start":"2025-12-01","period_end":"2025-12-31","messages_sent":150,"messages_received":23,"messages_failed":2,"units_used":167,"cost":165.33,"currency":"NOK","price_per_unit":0.99,"outbound_cost":165.33,"inbound_cost":0.0}},"SMSStatsPeriod":{"type":"object","description":"Statistics for a specific time period","properties":{"messages_sent":{"type":"integer","description":"Outbound messages"},"messages_received":{"type":"integer","description":"Inbound messages"},"units_used":{"type":"integer","description":"SMS units consumed"},"delivery_rate":{"type":"number","format":"float","description":"Delivery success rate (0-1)","example":0.95}}},"SMSStatsResponse":{"type":"object","description":"Aggregated SMS statistics across time periods","properties":{"today":{"$ref":"#/components/schemas/SMSStatsPeriod"},"this_week":{"$ref":"#/components/schemas/SMSStatsPeriod"},"this_month":{"$ref":"#/components/schemas/SMSStatsPeriod"},"last_30_days":{"$ref":"#/components/schemas/SMSStatsPeriod"},"status_breakdown":{"type":"object","description":"Message count by status","additionalProperties":{"type":"integer"},"example":{"QUEUED":5,"SENT":10,"DELIVERED":130,"FAILED":5}}},"example":{"today":{"messages_sent":12,"messages_received":3,"units_used":14,"delivery_rate":0.92},"this_week":{"messages_sent":45,"messages_received":8,"units_used":52,"delivery_rate":0.95},"this_month":{"messages_sent":150,"messages_received":23,"units_used":167,"delivery_rate":0.98},"last_30_days":{"messages_sent":180,"messages_received":28,"units_used":201,"delivery_rate":0.97},"status_breakdown":{"QUEUED":5,"SENT":10,"DELIVERED":130,"FAILED":5}}},"SMSKeyword":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"keyword":{"type":"string"},"description":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"webhook_url":{"type":"string","nullable":true},"auto_reply_enabled":{"type":"boolean"},"auto_reply_message":{"type":"string","nullable":true},"messages_received":{"type":"integer"},"last_message_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"550e8400-e29b-41d4-a716-446655440000","keyword":"SUPPORT","description":"Customer support keyword","is_active":true,"webhook_url":"https://example.com/webhooks/sms","auto_reply_enabled":true,"auto_reply_message":"Thank you for contacting support. We will respond shortly.","messages_received":42,"created_at":"2025-01-15T10:00:00Z"}},"CreateKeywordRequest":{"type":"object","required":["keyword"],"properties":{"keyword":{"type":"string","minLength":1,"maxLength":20,"description":"Keyword (alphanumeric, case-insensitive)"},"description":{"type":"string","maxLength":255},"webhook_url":{"type":"string","format":"uri"},"auto_reply_message":{"type":"string","maxLength":1600}},"example":{"keyword":"SUPPORT","description":"Customer support keyword","webhook_url":"https://example.com/webhooks/sms","auto_reply_message":"Thank you for contacting support."}},"UpdateKeywordRequest":{"type":"object","properties":{"description":{"type":"string","maxLength":255},"webhook_url":{"type":"string","format":"uri","nullable":true},"auto_reply_enabled":{"type":"boolean"},"auto_reply_message":{"type":"string","maxLength":1600,"nullable":true},"is_active":{"type":"boolean"}},"example":{"description":"Customer support keyword","webhook_url":"https://example.com/webhooks/sms","auto_reply_enabled":true,"auto_reply_message":"Thank you for contacting support. We will respond shortly."}},"AccountProfile":{"type":"object","properties":{"customer_id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"},"company":{"type":"string"},"customer_type":{"type":"string","enum":["private","business"]},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"example":{"customer_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","email":"kristen.nygaard@simula.no","first_name":"Kristen","last_name":"Nygaard","company":"Simula Research Laboratory","customer_type":"business","status":"active","created_at":"2024-01-15T09:30:00Z"}},"APIKeyItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key_prefix":{"type":"string"},"key_type":{"type":"string","enum":["service","account"]},"service":{"type":"string"},"description":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":"string","format":"date-time"}},"example":{"id":"a8b9c0d1-e2f3-4567-89ab-cdef01234567","key_prefix":"wayscloud_2xK9mPq","key_type":"service","service":"database","description":"Production database access","scopes":["database:read","database:write"],"is_active":true,"created_at":"2024-03-01T14:00:00Z","last_used_at":"2024-03-15T08:45:22Z"}},"APIKeyList":{"type":"array","items":{"$ref":"#/components/schemas/APIKeyItem"},"example":[{"id":"a8b9c0d1-e2f3-4567-89ab-cdef01234567","key_prefix":"wayscloud_2xK9mPq","key_type":"service","service":"database","is_active":true},{"id":"b9c0d1e2-f3a4-5678-9abc-def012345678","key_prefix":"wayscloud_pat_7Zk","key_type":"account","service":null,"is_active":true}]},"CreateServiceKeyRequest":{"type":"object","required":["service"],"properties":{"service":{"type":"string","enum":["database","storage","llm-api","dns","vps"]},"description":{"type":"string"}},"example":{"service":"database","description":"CI/CD pipeline database access"}},"CreatePATRequest":{"type":"object","required":["scopes"],"properties":{"scopes":{"type":"array","items":{"type":"string"}},"description":{"type":"string"},"expires_in_days":{"type":"integer","minimum":1,"maximum":365}},"example":{"scopes":["api_keys:write","ssh_keys:read","services:read"],"description":"Terraform automation token","expires_in_days":90}},"APIKeyCreated":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"key":{"type":"string","description":"Full key - shown only once"},"key_prefix":{"type":"string"},"key_type":{"type":"string"},"warning":{"type":"string"}},"example":{"id":"c0d1e2f3-a4b5-6789-abcd-ef0123456789","key":"wayscloud_3mN8pQrStUvWxYz_AbCdEfGhIjKlMnOpQrStUvWxYz0123456789","key_prefix":"wayscloud_3mN8pQr","key_type":"service","warning":"Save this key now. It cannot be retrieved later."}},"SSHKeyItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"fingerprint":{"type":"string"},"key_type":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"vm_count":{"type":"integer"}},"example":{"id":"d1e2f3a4-b5c6-7890-abcd-ef1234567890","name":"deploy-server","fingerprint":"SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8","key_type":"ssh-ed25519","created_at":"2024-02-20T11:15:00Z","vm_count":3}},"SSHKeyList":{"type":"array","items":{"$ref":"#/components/schemas/SSHKeyItem"},"example":[{"id":"d1e2f3a4-b5c6-7890-abcd-ef1234567890","name":"deploy-server","fingerprint":"SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8","key_type":"ssh-ed25519","vm_count":3},{"id":"e2f3a4b5-c6d7-8901-bcde-f23456789012","name":"github-actions","fingerprint":"SHA256:kR9H2mK5pL3nQ8vX1cB4jT6wY0zA7sD2fG5hJ8kL9mN","key_type":"ssh-rsa","vm_count":1}]},"CreateSSHKeyRequest":{"type":"object","required":["name","public_key"],"properties":{"name":{"type":"string"},"public_key":{"type":"string"}},"example":{"name":"deploy-server","public_key":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl user@workstation"}},"ServiceItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"service_type":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"region":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"metadata":{"type":"object"}},"example":{"id":"f3a4b5c6-d7e8-9012-cdef-345678901234","service_type":"database","name":"webapp-prod-db","status":"running","region":"oslo","created_at":"2024-02-01T10:00:00Z","metadata":{"engine":"postgresql","version":"16"}}},"ServiceList":{"type":"array","items":{"$ref":"#/components/schemas/ServiceItem"},"example":[{"id":"f3a4b5c6-d7e8-9012-cdef-345678901234","service_type":"database","name":"webapp-prod-db","status":"running","region":"oslo"},{"id":"a4b5c6d7-e8f9-0123-def4-567890123456","service_type":"storage","name":"company-assets","status":"active","region":"oslo"}]},"DeleteSuccess":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"example":{"success":true,"message":"Resource deleted successfully"}},"Error":{"type":"object","properties":{"detail":{"type":"string"}},"example":{"detail":"Resource not found"}},"ReasoningMetadata":{"properties":{"summary":{"type":"string","title":"Summary","description":"Brief summary of the reasoning process"},"depth":{"anyOf":[{"type":"string","enum":["shallow","standard","deep","critical"]},{"type":"null"}],"title":"Depth","description":"Reasoning depth level","default":"standard"},"steps":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Steps","description":"Individual reasoning steps taken"},"tokens":{"type":"integer","title":"Tokens","description":"Number of tokens used for reasoning"},"raw_thinking":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Raw Thinking","description":"Raw thinking content from the model (if available)"}},"type":"object","required":["summary","tokens"],"title":"ReasoningMetadata","description":"Metadata from reasoning-capable models (deepseek-r1, qwen3-*-thinking).\n\nWhen using reasoning models, the AI may perform chain-of-thought reasoning\nbefore generating its final response. This metadata captures that process."},"ReasoningContainer":{"type":"object","description":"Container for reasoning metadata returned by reasoning-capable models","properties":{"reasoning":{"$ref":"#/components/schemas/ReasoningMetadata"}},"example":{"reasoning":{"summary":"Model evaluated alternative interpretations and selected the most likely answer.","depth":"standard","steps":["Parsed user intent","Evaluated context","Performed logical deduction","Selected final response"],"tokens":214}}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError","example":{"loc":["body","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError","example":{"detail":[{"loc":["body","db_type"],"msg":"Input should be 'postgresql' or 'mariadb'","type":"enum"}]}},"VPSInstance":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"hostname":{"type":"string"},"display_name":{"type":"string"},"status":{"type":"string","enum":["running","stopped","starting","stopping","provisioning"]},"plan_code":{"type":"string"},"region":{"type":"string"},"os_template":{"type":"string"},"ipv4_address":{"type":"string"},"ipv6_address":{"type":"string"},"vcpus":{"type":"integer"},"memory_gb":{"type":"integer"},"disk_gb":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","hostname":"web01.example.com","display_name":"Production Web Server","status":"running","plan_code":"vps-4gb-2cpu","region":"oslo","os_template":"ubuntu-22.04","ipv4_address":"185.35.202.45","ipv6_address":"2a01:4f9:c010:1234::1","vcpus":2,"memory_gb":4,"disk_gb":80,"created_at":"2024-02-15T14:30:00Z"}},"VPSList":{"type":"object","properties":{"instances":{"type":"array","items":{"$ref":"#/components/schemas/VPSInstance"}},"total":{"type":"integer"}},"example":{"instances":[{"id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","hostname":"web01.example.com","display_name":"Production Web Server","status":"running","ipv4_address":"185.35.202.45"},{"id":"c6d7e8f9-a0b1-2345-6789-0abcdef12345","hostname":"db01.example.com","display_name":"Database Server","status":"running","ipv4_address":"185.35.202.46"}],"total":2}},"VPSStatus":{"type":"object","properties":{"status":{"type":"string"},"uptime_seconds":{"type":"integer"},"cpu_usage_percent":{"type":"number"},"memory_usage_percent":{"type":"number"},"disk_usage_percent":{"type":"number"},"network_in_bytes":{"type":"integer"},"network_out_bytes":{"type":"integer"}},"example":{"status":"running","uptime_seconds":2592000,"cpu_usage_percent":12.5,"memory_usage_percent":68.3,"disk_usage_percent":42.1,"network_in_bytes":157286400,"network_out_bytes":52428800}},"VPSAction":{"type":"object","properties":{"success":{"type":"boolean"},"action":{"type":"string"},"message":{"type":"string"}},"example":{"success":true,"action":"start","message":"VPS is starting"}},"VPSCreated":{"type":"object","description":"Response when a new VPS is created","properties":{"id":{"type":"string","format":"uuid","description":"VPS instance ID"},"hostname":{"type":"string"},"status":{"type":"string","enum":["provisioning","running"]},"ipv4":{"type":"string","description":"Primary IPv4 address"},"ipv6":{"type":"string","description":"Primary IPv6 address"}},"example":{"id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","hostname":"web01.example.com","status":"provisioning","ipv4":"185.35.202.45","ipv6":"2a01:4f9:c010:1234::1"}},"VPSSnapshot":{"type":"object","properties":{"snapshot_id":{"type":"string","format":"uuid"},"vps_id":{"type":"string","format":"uuid"},"snapshot_name":{"type":"string"},"description":{"type":"string","nullable":true},"status":{"type":"string","enum":["available","creating","deleting","error"]},"created_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true}},"example":{"snapshot_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","vps_id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","snapshot_name":"before_upgrade","description":"Pre-upgrade state","status":"available","created_at":"2026-04-03T03:00:00Z","completed_at":"2026-04-03T03:00:02Z"}},"VPSSnapshotCreate":{"type":"object","required":["snapshot_name"],"properties":{"snapshot_name":{"type":"string","minLength":3,"maxLength":100,"description":"Snapshot name (alphanumeric + underscore)"},"description":{"type":"string","maxLength":500,"description":"Optional description"}},"example":{"snapshot_name":"before-upgrade","description":"Snapshot before OS upgrade"}},"VPSBackup":{"type":"object","properties":{"backup_id":{"type":"string","format":"uuid"},"vps_id":{"type":"string","format":"uuid"},"backup_type":{"type":"string","enum":["automatic","manual"]},"size_gb":{"type":"number","nullable":true},"status":{"type":"string","enum":["creating","uploading","available","restoring","deleting","error"]},"started_at":{"type":"string","format":"date-time"},"completed_at":{"type":"string","format":"date-time","nullable":true},"expires_at":{"type":"string","format":"date-time","nullable":true}},"example":{"backup_id":"c1d2e3f4-a5b6-7890-cdef-123456789abc","vps_id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","backup_type":"automatic","size_gb":3.56,"status":"available","started_at":"2026-04-03T03:00:00Z","completed_at":"2026-04-03T03:02:30Z","expires_at":"2026-04-10T03:00:00Z"}},"VPSBackupPolicy":{"type":"object","properties":{"policy_id":{"type":"string","format":"uuid"},"vps_id":{"type":"string","format":"uuid"},"enabled":{"type":"boolean"},"frequency":{"type":"string","enum":["daily","weekly"]},"time_of_day":{"type":"string","description":"HH:MM in UTC"},"day_of_week":{"type":"integer","minimum":0,"maximum":6,"nullable":true,"description":"0=Monday, only for weekly"},"retention_days":{"type":"integer","minimum":1,"maximum":90},"next_backup_at":{"type":"string","format":"date-time","nullable":true},"last_backup_at":{"type":"string","format":"date-time","nullable":true}},"example":{"policy_id":"d1e2f3a4-b5c6-7890-defa-bcdef1234567","vps_id":"b5c6d7e8-f9a0-1234-5678-90abcdef1234","enabled":true,"frequency":"daily","time_of_day":"03:00","retention_days":7,"next_backup_at":"2026-04-04T03:00:00Z"}},"VPSBackupPolicySet":{"type":"object","properties":{"enabled":{"type":"boolean","default":true},"frequency":{"type":"string","enum":["daily","weekly"],"default":"daily"},"time_of_day":{"type":"string","default":"03:00","description":"HH:MM in UTC"},"day_of_week":{"type":"integer","minimum":0,"maximum":6,"nullable":true,"description":"0=Mon, required for weekly"},"retention_days":{"type":"integer","minimum":1,"maximum":90,"default":7}},"example":{"enabled":true,"frequency":"daily","time_of_day":"03:00","retention_days":7}},"AppPlan":{"type":"object","properties":{"id":{"type":"string","description":"Plan identifier"},"name":{"type":"string","description":"Plan display name"},"description":{"type":"string","nullable":true},"cpu_cores":{"type":"number","description":"CPU cores allocated per instance"},"memory_mb":{"type":"integer","description":"Memory in MB per instance"},"disk_gb":{"type":"integer","description":"Disk space in GB"},"max_instances":{"type":"integer","description":"Maximum concurrent instances"},"max_apps_per_customer":{"type":"integer"},"max_custom_domains":{"type":"integer"},"bandwidth_gb_included":{"type":"integer","description":"Monthly bandwidth included"},"price_monthly":{"type":"number","description":"Monthly subscription price"},"hourly_rate":{"type":"number","nullable":true,"description":"Per instance-hour usage rate"},"currency":{"type":"string"},"features":{"type":"object","additionalProperties":true}},"example":{"id":"app-starter","name":"Starter","description":"For small production workloads","cpu_cores":1.0,"memory_mb":1024,"disk_gb":10,"max_instances":2,"max_apps_per_customer":5,"max_custom_domains":3,"bandwidth_gb_included":100,"price_monthly":99.0,"hourly_rate":0.15,"currency":"NOK","features":{}}},"AppRegion":{"type":"object","properties":{"code":{"type":"string","description":"Region code"},"name":{"type":"string","description":"Region display name"},"country_code":{"type":"string","description":"ISO 3166-1 alpha-2"},"app_platform_available":{"type":"boolean"}},"example":{"code":"no","name":"Norge","country_code":"NO","app_platform_available":true}},"AppSummary":{"type":"object","description":"App summary for list views","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"short_id":{"type":"string"},"region":{"type":"string"},"plan_id":{"type":"string"},"plan_name":{"type":"string"},"status":{"type":"string","enum":["creating","building","deploying","running","stopped","stopping","error","failed","deleting"]},"default_url":{"type":"string","nullable":true},"instance_count":{"type":"integer"},"running_instances":{"type":"integer"},"max_instances":{"type":"integer"},"domain_count":{"type":"integer"},"scale_to_zero_enabled":{"type":"boolean"},"is_scaled_to_zero":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"last_deployed_at":{"type":"string","format":"date-time","nullable":true},"plan_max_apps":{"type":"integer"},"plan_max_domains":{"type":"integer"}},"example":{"id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","name":"invoice-api-prod","slug":"invoice-api-prod","short_id":"q7b6t1y0","region":"no","plan_id":"app-starter","plan_name":"Starter","status":"running","default_url":"invoice-api-prod-q7b6t1y0.apps.wayscloud.services","instance_count":1,"running_instances":1,"max_instances":2,"domain_count":0,"scale_to_zero_enabled":false,"is_scaled_to_zero":false,"created_at":"2026-03-15T09:30:00Z","last_deployed_at":"2026-04-10T14:22:00Z","plan_max_apps":5,"plan_max_domains":3}},"AppList":{"type":"object","description":"Paginated list of apps","properties":{"apps":{"type":"array","items":{"$ref":"#/components/schemas/AppSummary"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"}},"example":{"apps":[{"id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","name":"invoice-api-prod","slug":"invoice-api-prod","short_id":"q7b6t1y0","region":"no","plan_id":"app-starter","plan_name":"Starter","status":"running","default_url":"invoice-api-prod-q7b6t1y0.apps.wayscloud.services","instance_count":1,"running_instances":1,"max_instances":2,"domain_count":0,"scale_to_zero_enabled":false,"is_scaled_to_zero":false,"created_at":"2026-03-15T09:30:00Z","last_deployed_at":"2026-04-10T14:22:00Z","plan_max_apps":5,"plan_max_domains":3}],"total":1,"page":1,"page_size":20}},"App":{"type":"object","description":"Full app details","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"short_id":{"type":"string"},"region":{"type":"string"},"plan_id":{"type":"string"},"plan_name":{"type":"string"},"status":{"type":"string","enum":["creating","building","deploying","running","stopped","stopping","error","failed","deleting"]},"status_message":{"type":"string","nullable":true},"default_url":{"type":"string","nullable":true},"port":{"type":"integer"},"health_check_path":{"type":"string"},"min_instances":{"type":"integer"},"max_instances":{"type":"integer"},"scale_to_zero_enabled":{"type":"boolean"},"idle_timeout_minutes":{"type":"integer"},"is_scaled_to_zero":{"type":"boolean"},"active_revision_id":{"type":"string","nullable":true},"active_deployment_id":{"type":"string","nullable":true},"last_deployed_at":{"type":"string","format":"date-time","nullable":true},"active_image":{"type":"string","nullable":true,"description":"Currently deployed container image URI"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time","nullable":true},"volume":{"type":"object","nullable":true,"description":"Persistent volume metadata. Null if no volume attached."}},"example":{"id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","name":"invoice-api-prod","slug":"invoice-api-prod","short_id":"q7b6t1y0","region":"no","plan_id":"app-starter","plan_name":"Starter","status":"running","status_message":null,"default_url":"invoice-api-prod-q7b6t1y0.apps.wayscloud.services","port":3000,"health_check_path":"/health","min_instances":1,"max_instances":2,"scale_to_zero_enabled":false,"idle_timeout_minutes":15,"is_scaled_to_zero":false,"active_revision_id":"rev_01jxr5n4p8m2w7k3q9b6t0y1z","active_deployment_id":"dep_01jxr5n4p8m2w7k3q9b6t0y1z","last_deployed_at":"2026-04-10T14:22:00Z","active_image":"ghcr.io/acme-corp/invoice-api:v2.4.1","created_at":"2026-03-15T09:30:00Z","updated_at":"2026-04-10T14:22:00Z","volume":null}},"AppDeployResponse":{"type":"object","description":"Deployment initiated response","properties":{"app_id":{"type":"string"},"revision_id":{"type":"string"},"deployment_id":{"type":"string"},"status":{"type":"string","description":"Initial deployment status (pending)"},"message":{"type":"string"},"preflight_warnings":{"type":"array","items":{"type":"object"},"description":"Warnings from pre-deploy checks"}},"example":{"app_id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","revision_id":"rev_01jxr6a2b3c4d5e6f7g8h9i0j","deployment_id":"dep_01jxr6a2b3c4d5e6f7g8h9i0j","status":"pending","message":"Deployment started for image ghcr.io/acme-corp/invoice-api:v2.4.1","preflight_warnings":[]}},"AppAction":{"type":"object","description":"Result of an app lifecycle action","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"example":{"success":true,"message":"App started"}},"AppLogs":{"type":"object","description":"Container log output. This is live stdout/stderr from the running container, not a historical log archive. Logs may be empty for stopped or idle apps.","properties":{"app_id":{"type":"string"},"instance_id":{"type":"string","nullable":true},"logs":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time"},"message":{"type":"string"},"stream":{"type":"string","enum":["stdout","stderr"]}}},"description":"Log entries from container output"},"has_more":{"type":"boolean","description":"True if more log lines are available beyond the requested limit"}},"example":{"app_id":"app_01jxr4m8k2w9n5v3q7b6t1y0z","instance_id":null,"logs":[{"timestamp":"2026-04-12T14:22:01Z","message":"Server listening on port 3000","stream":"stdout"},{"timestamp":"2026-04-12T14:22:03Z","message":"Connected to database","stream":"stdout"}],"has_more":false}},"StorageBucket":{"type":"object","properties":{"name":{"type":"string"},"region":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"size_bytes":{"type":"integer"},"object_count":{"type":"integer"},"is_public":{"type":"boolean"}},"example":{"name":"company-assets","region":"oslo","created_at":"2024-01-20T09:00:00Z","size_bytes":53687091200,"object_count":12458,"is_public":false}},"StorageBucketList":{"type":"object","properties":{"buckets":{"type":"array","items":{"$ref":"#/components/schemas/StorageBucket"}},"total":{"type":"integer"}},"example":{"buckets":[{"name":"company-assets","region":"oslo","size_bytes":53687091200,"object_count":12458,"is_public":false},{"name":"app-backups","region":"oslo","size_bytes":107374182400,"object_count":89,"is_public":false}],"total":2}},"S3Credentials":{"type":"object","properties":{"access_key_id":{"type":"string"},"secret_access_key":{"type":"string"},"endpoint_url":{"type":"string"},"region":{"type":"string"}},"example":{"access_key_id":"AKWC3XJQK7HMNTUVWXYZ","secret_access_key":"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY","endpoint_url":"https://storage.wayscloud.services","region":"oslo"}},"StorageBucketCreated":{"type":"object","description":"Response when a new bucket is created","properties":{"success":{"type":"boolean"},"bucket_name":{"type":"string"},"message":{"type":"string"},"endpoint":{"type":"string"},"region":{"type":"string"}},"example":{"success":true,"bucket_name":"my-app-uploads","message":"Bucket 'my-app-uploads' created successfully","endpoint":"https://storage.wayscloud.services","region":"oslo"}},"DNSZone":{"type":"object","properties":{"name":{"type":"string"},"status":{"type":"string","enum":["active","pending","disabled"]},"dnssec_enabled":{"type":"boolean"},"record_count":{"type":"integer"},"nameservers":{"type":"array","items":{"type":"string"}},"created_at":{"type":"string","format":"date-time"}},"example":{"name":"example.com","status":"active","dnssec_enabled":true,"record_count":24,"nameservers":["grieg.wayscloud.no","bohr.wayscloud.dk","lindgren.wayscloud.se","aalto.wayscloud.fi"],"created_at":"2024-01-10T08:00:00Z"}},"DNSZoneList":{"type":"object","properties":{"zones":{"type":"array","items":{"$ref":"#/components/schemas/DNSZone"}},"total":{"type":"integer"}},"example":{"zones":[{"name":"example.com","status":"active","dnssec_enabled":true,"record_count":24},{"name":"myapp.io","status":"active","dnssec_enabled":false,"record_count":8}],"total":2}},"DNSRecord":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"type":{"type":"string","enum":["A","AAAA","CNAME","MX","TXT","NS","SRV","CAA"]},"content":{"type":"string"},"ttl":{"type":"integer"},"priority":{"type":"integer"}},"example":{"id":"e8f9a0b1-c2d3-4567-890a-bcdef1234567","name":"www","type":"A","content":"185.35.202.45","ttl":3600,"priority":null}},"DNSRecordList":{"type":"object","properties":{"records":{"type":"array","items":{"$ref":"#/components/schemas/DNSRecord"}},"total":{"type":"integer"}},"example":{"records":[{"id":"e8f9a0b1-c2d3-4567-890a-bcdef1234567","name":"@","type":"A","content":"185.35.202.45","ttl":3600},{"id":"f9a0b1c2-d3e4-5678-90ab-cdef12345678","name":"www","type":"CNAME","content":"example.com","ttl":3600},{"id":"a0b1c2d3-e4f5-6789-0abc-def123456789","name":"@","type":"MX","content":"mail.example.com","ttl":3600,"priority":10}],"total":3}},"DNSStatistics":{"type":"object","properties":{"queries_today":{"type":"integer"},"queries_this_month":{"type":"integer"},"top_record_types":{"type":"object"},"response_time_ms_avg":{"type":"number"}},"example":{"queries_today":15420,"queries_this_month":425000,"top_record_types":{"A":8500,"AAAA":4200,"MX":1800,"TXT":920},"response_time_ms_avg":12.5}},"DNSSECInfo":{"type":"object","properties":{"enabled":{"type":"boolean"},"ds_records":{"type":"array","items":{"type":"string"}},"algorithm":{"type":"string"}},"example":{"enabled":true,"ds_records":["example.com. 3600 IN DS 12345 13 2 49FD46E6C4B45C55D4AC49FD46E6C4B45C55D4AC..."],"algorithm":"ECDSAP256SHA256"}},"Region":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"country":{"type":"string"},"available_services":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","maintenance","deprecated"]}},"example":{"code":"oslo","name":"Oslo, Norway","country":"NO","available_services":["database","storage","vps","dns","llm"],"status":"active"}},"RegionList":{"type":"object","properties":{"regions":{"type":"array","items":{"$ref":"#/components/schemas/Region"}},"total":{"type":"integer"}},"example":{"regions":[{"code":"oslo","name":"Oslo, Norway","country":"NO","status":"active"},{"code":"stockholm","name":"Stockholm, Sweden","country":"SE","status":"active"}],"total":2}},"RegionPricing":{"type":"object","properties":{"region":{"type":"string"},"currency":{"type":"string"},"services":{"type":"object"}},"example":{"region":"oslo","currency":"NOK","services":{"vps":{"vps-2gb-1cpu":99,"vps-4gb-2cpu":199,"vps-8gb-4cpu":399},"storage":{"per_gb_month":0.5},"database":{"postgresql_per_gb":2.5,"mariadb_per_gb":2.0}}}},"DatabaseInstance":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"friendly_name":{"type":"string","description":"User-chosen display name"},"technical_name":{"type":"string","description":"System-generated name"},"database_type":{"type":"string","enum":["postgresql","mariadb"]},"version":{"type":"string"},"status":{"type":"string","enum":["provisioning","active","maintenance","suspended","deleted"]},"region":{"type":"string"},"storage_gb":{"type":"number"},"created_at":{"type":"string","format":"date-time"},"connection":{"$ref":"#/components/schemas/DatabaseCredentials"}},"example":{"id":"f8c3b2a1-9d4e-5f6a-8b7c-0d1e2f3a4b5c","friendly_name":"webapp_prod_db","technical_name":"cust_6dd1d906_pg_20251120150438_35bf9f71","database_type":"postgresql","version":"16","status":"active","region":"oslo","storage_gb":2.4,"created_at":"2025-11-20T15:04:38Z","connection":{"host":"pg.dbaas.wayscloud.services","port":5432,"database":"cust_6dd1d906_pg_20251120150438_35bf9f71","username":"app_user","connection_string":"postgresql://app_user:***@pg.dbaas.wayscloud.services:5432/cust_6dd1d906_pg_20251120150438_35bf9f71"}}},"DatabaseList":{"type":"object","properties":{"databases":{"type":"array","items":{"$ref":"#/components/schemas/DatabaseInstance"}},"total":{"type":"integer"}},"example":{"databases":[{"id":"f8c3b2a1-9d4e-5f6a-8b7c-0d1e2f3a4b5c","friendly_name":"webapp_prod_db","database_type":"postgresql","version":"16","status":"active","storage_gb":2.4},{"id":"a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d","friendly_name":"analytics_db","database_type":"mariadb","version":"11.4","status":"active","storage_gb":0.8}],"total":2}},"DatabaseEngine":{"type":"object","properties":{"name":{"type":"string"},"versions":{"type":"array","items":{"type":"string"}},"default_version":{"type":"string"}},"example":{"name":"postgresql","versions":["14","15","16"],"default_version":"16"}},"DatabaseEngineList":{"type":"object","properties":{"engines":{"type":"array","items":{"$ref":"#/components/schemas/DatabaseEngine"}}},"example":{"engines":[{"name":"postgresql","versions":["14","15","16"],"default_version":"16"},{"name":"mariadb","versions":["10.11","11.4"],"default_version":"11.4"}]}},"DatabaseCredentials":{"type":"object","properties":{"host":{"type":"string","description":"Database server hostname"},"port":{"type":"integer","description":"Database port"},"database":{"type":"string","description":"Database name"},"username":{"type":"string","description":"Database username"},"password":{"type":"string","description":"Database password (only shown once at creation)"},"connection_string":{"type":"string","description":"Complete connection string"}},"example":{"host":"pg.dbaas.wayscloud.services","port":5432,"database":"cust_6dd1d906_pg_20251120150438_35bf9f71","username":"app_user","password":"Xk9mN2pQ4rS7tV3w","connection_string":"postgresql://app_user:Xk9mN2pQ4rS7tV3w@pg.dbaas.wayscloud.services:5432/cust_6dd1d906_pg_20251120150438_35bf9f71"}},"DatabaseMetrics":{"type":"object","properties":{"database_id":{"type":"string","format":"uuid"},"storage_gb":{"type":"number","description":"Storage used in GB"},"backup_gb":{"type":"number","description":"Backup storage in GB"},"active_connections":{"type":"integer","description":"Current active connections"},"max_connections":{"type":"integer","description":"Maximum connections allowed"},"total_queries":{"type":"integer","description":"Total queries executed"},"queries_per_sec":{"type":"number","description":"Queries per second"},"cache_hit_ratio":{"type":"number","description":"Cache hit ratio percentage"},"cpu_percent":{"type":"number","description":"CPU usage percentage"},"memory_percent":{"type":"number","description":"Memory usage percentage"},"measured_at":{"type":"string","format":"date-time"}},"example":{"database_id":"f8c3b2a1-9d4e-5f6a-8b7c-0d1e2f3a4b5c","storage_gb":2.4,"backup_gb":1.2,"active_connections":8,"max_connections":100,"total_queries":1247832,"queries_per_sec":42.7,"cache_hit_ratio":98.4,"cpu_percent":12.3,"memory_percent":34.8,"measured_at":"2025-11-27T14:30:00Z"}},"DatabaseSnapshot":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"database_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"size_gb":{"type":"number"},"status":{"type":"string","enum":["creating","available","restoring","failed"]},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d","database_id":"f8c3b2a1-9d4e-5f6a-8b7c-0d1e2f3a4b5c","name":"before_migration_v2","size_gb":2.1,"status":"available","created_at":"2025-11-20T10:00:00Z"}},"DatabaseSnapshotList":{"type":"object","properties":{"snapshots":{"type":"array","items":{"$ref":"#/components/schemas/DatabaseSnapshot"}},"total":{"type":"integer"}},"example":{"snapshots":[{"id":"9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d","name":"before_migration_v2","size_gb":2.1,"status":"available","created_at":"2025-11-20T10:00:00Z"},{"id":"b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e","name":"weekly_backup_47","size_gb":1.9,"status":"available","created_at":"2025-11-17T03:00:00Z"}],"total":2}},"DatabaseHistory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"action":{"type":"string","enum":["created","deleted","restored","modified","snapshot_created"]},"database_name":{"type":"string"},"database_type":{"type":"string"},"timestamp":{"type":"string","format":"date-time"},"details":{"type":"object"}},"example":{"id":"c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f","action":"created","database_name":"webapp_prod_db","database_type":"postgresql","timestamp":"2025-11-20T15:04:38Z","details":{"version":"16","region":"oslo"}}},"DatabaseQuota":{"type":"object","properties":{"used":{"type":"integer","description":"Number of databases created"},"limit":{"type":"integer","description":"Maximum databases allowed"},"storage_used_gb":{"type":"number"},"storage_limit_gb":{"type":"number"}},"example":{"used":2,"limit":10,"storage_used_gb":3.2,"storage_limit_gb":50.0}},"OperationSuccess":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}},"example":{"success":true,"message":"Operation completed successfully"}},"OpenAIModel":{"type":"object","properties":{"id":{"type":"string","description":"Model identifier","example":"mixtral-8x7b"},"object":{"type":"string","enum":["model"],"default":"model"},"owned_by":{"type":"string","example":"wayscloud"},"created":{"type":"integer","description":"Unix timestamp"}},"example":{"id":"mixtral-8x7b","object":"model","owned_by":"wayscloud","created":1700000000}},"OpenAIModelList":{"type":"object","properties":{"object":{"type":"string","enum":["list"],"default":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/OpenAIModel"}}},"example":{"object":"list","data":[{"id":"mixtral-8x7b","object":"model","owned_by":"wayscloud","created":1700000000},{"id":"deepseek-v3","object":"model","owned_by":"wayscloud","created":1700000000}]}},"DomainVerification":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"domain":{"type":"string","description":"Domain name (normalized to lowercase/punycode)"},"purpose":{"type":"string","enum":["email","custom_host","webhook","link_branding","verify_channel","other"]},"status":{"type":"string","enum":["pending","verified","failed","revoked"]},"verification_method":{"type":"string","enum":["DNS_TXT","DNS_CNAME"]},"verification_token":{"type":"string","description":"Token to add to DNS record"},"dns_instructions":{"type":"object","description":"Instructions for DNS setup"},"verification_attempts":{"type":"integer"},"verified_at":{"type":"string","format":"date-time"},"last_checked_at":{"type":"string","format":"date-time"},"failed_reason":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","domain":"example.com","purpose":"email","status":"pending","verification_method":"DNS_TXT","verification_token":"wayscloud-domain-verification=abc123xyz789","dns_instructions":{"record_type":"TXT","host":"_wayscloud.example.com","value":"wayscloud-domain-verification=abc123xyz789"},"verification_attempts":0,"verified_at":null,"last_checked_at":null,"failed_reason":null,"created_at":"2025-12-08T10:00:00Z"}},"DomainVerificationSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"purpose":{"type":"string"},"status":{"type":"string"},"verification_method":{"type":"string"},"verified_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","domain":"example.com","purpose":"email","status":"verified","verification_method":"DNS_TXT","verified_at":"2025-12-08T10:30:00Z","created_at":"2025-12-08T10:00:00Z"}},"DomainVerificationList":{"type":"object","properties":{"domains":{"type":"array","items":{"$ref":"#/components/schemas/DomainVerificationSummary"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"}},"example":{"domains":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","domain":"example.com","purpose":"email","status":"verified"},{"id":"b2c3d4e5-f6a7-8901-bcde-f23456789012","domain":"app.example.com","purpose":"custom_host","status":"pending"}],"total":2,"page":1,"page_size":20}},"RegisterDomainRequest":{"type":"object","required":["domain","purpose"],"properties":{"domain":{"type":"string","description":"Domain to verify (e.g., example.com)"},"purpose":{"type":"string","enum":["email","custom_host","webhook","link_branding","verify_channel","other"]},"verification_method":{"type":"string","enum":["DNS_TXT","DNS_CNAME"],"default":"DNS_TXT"},"metadata":{"type":"object","description":"Optional metadata"}},"example":{"domain":"example.com","purpose":"email","verification_method":"DNS_TXT"}},"VerifyDomainResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"domain":{"type":"string"},"status":{"type":"string"},"verified":{"type":"boolean"},"message":{"type":"string"},"dns_records_found":{"type":"array","items":{"type":"string"}},"checked_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","domain":"example.com","status":"verified","verified":true,"message":"Domain verified successfully","dns_records_found":["wayscloud-domain-verification=abc123xyz789"],"checked_at":"2025-12-08T10:30:00Z"}},"DomainVerificationHistory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"event_type":{"type":"string"},"previous_status":{"type":"string"},"new_status":{"type":"string"},"dns_response":{"type":"object"},"error_message":{"type":"string"},"performed_by":{"type":"string"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"c3d4e5f6-a7b8-9012-cdef-345678901234","event_type":"check_completed","previous_status":"pending","new_status":"verified","dns_response":{"records_found":true,"actual_values":["wayscloud-domain-verification=abc123xyz789"]},"performed_by":"api","created_at":"2025-12-08T10:30:00Z"}},"BackupPolicyRequest":{"properties":{"enabled":{"type":"boolean","title":"Enabled","description":"Enable/disable automated backups"},"frequency":{"type":"string","enum":["hourly","daily","weekly"],"title":"Frequency","description":"Backup frequency","default":"daily"},"retention_days":{"type":"integer","title":"Retention Days","description":"How many days to keep backups","default":7},"time_of_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Of Day","description":"Time for daily backups (HH:MM format)"}},"type":"object","required":["enabled"],"title":"BackupPolicyRequest","description":"Configure automatic backups for your database.\n\nSet up scheduled backups to protect your data. Choose how often to back up\nand how long to keep backups. Daily backups at 02:00 with 7-day retention\nis recommended for most use cases.","example":{"enabled":true,"frequency":"daily","retention_days":7,"time_of_day":"02:00"}},"BackupPolicyResponse":{"properties":{"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"enabled":{"type":"boolean","title":"Enabled"},"frequency":{"type":"string","title":"Frequency"},"retention_days":{"type":"integer","title":"Retention Days"},"time_of_day":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Time Of Day"},"last_backup":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Backup"},"next_backup":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Backup"}},"type":"object","required":["db_name","db_type","enabled","frequency","retention_days"],"title":"BackupPolicyResponse","description":"Your current backup policy settings.\n\nShows when the last backup ran and when the next one is scheduled.\nBackups older than `retention_days` are automatically deleted.","example":{"db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","enabled":true,"frequency":"daily","retention_days":7,"time_of_day":"02:00","last_backup":"2025-11-26T02:00:15Z","next_backup":"2025-11-27T02:00:00Z"}},"DatabaseCreateRequest":{"properties":{"db_type":{"type":"string","enum":["postgresql","mariadb"],"title":"Db Type","description":"Database type (postgresql or mariadb)"},"requested_name":{"anyOf":[{"type":"string","maxLength":30,"pattern":"^[a-zA-Z0-9_]+$"},{"type":"null"}],"title":"Requested Name","description":"Optional custom database name suffix (alphanumeric and underscore only, max 30 chars). Creates deterministic name: cust_{customer_id}_{requested_name}"}},"type":"object","required":["db_type"],"title":"DatabaseCreateRequest","description":"Create a new managed database.\n\nSimply specify the database type (PostgreSQL or MariaDB) and we'll provision\na fully managed database with auto-generated credentials. You'll receive the\nconnection details in the response.\n\n**Terraform users**: Use `requested_name` for deterministic, reproducible database names\nthat won't change between deployments.","example":{"db_type":"postgresql","requested_name":"webapp_prod_db"}},"DatabaseCreateResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"db_type":{"type":"string","title":"Db Type"},"db_name":{"type":"string","title":"Db Name"},"username":{"type":"string","title":"Username"},"password":{"type":"string","title":"Password"},"host":{"type":"string","title":"Host"},"port":{"type":"integer","title":"Port"},"connection_string":{"type":"string","title":"Connection String"},"message":{"type":"string","title":"Message"},"password_generated":{"type":"boolean","title":"Password Generated","description":"True if password was auto-generated","default":false},"idempotent":{"type":"boolean","title":"Idempotent","description":"True if database already existed (no new creation)","default":false}},"type":"object","required":["success","db_type","db_name","username","password","host","port","connection_string","message"],"title":"DatabaseCreateResponse","description":"Your new database connection details.\n\n**Important**: Save the `password` securely - it cannot be retrieved later.\nUse the `connection_string` to connect directly from your application.\n\nIf `idempotent` is true, the database already existed and you're receiving\nthe existing credentials (useful for Terraform re-runs).","example":{"success":true,"db_type":"postgresql","db_name":"cust_6dd1d906_pg_webapp_prod_db","username":"cust_6dd1d906_pg_webapp_prod_db","password":"Xk9mN2pQ4rS7tV3wYz","host":"db.no.wayscloud.services","port":5432,"connection_string":"postgresql://cust_6dd1d906_pg_webapp_prod_db:Xk9mN2pQ4rS7tV3wYz@db.no.wayscloud.services:5432/cust_6dd1d906_pg_webapp_prod_db","message":"Database created successfully","password_generated":true,"idempotent":false}},"DatabaseCredentialsResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"db_name":{"type":"string","title":"Db Name"},"username":{"type":"string","title":"Username"},"password":{"type":"string","title":"Password"},"host":{"type":"string","title":"Host"},"port":{"type":"integer","title":"Port"},"connection_string":{"type":"string","title":"Connection String"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"}},"type":"object","required":["success","db_name","username","password","host","port","connection_string"],"title":"DatabaseCredentialsResponse","description":"Your database credentials retrieved from secure storage.\n\nUse this if you've lost your connection details. The password is securely\nstored and can be retrieved at any time.","example":{"success":true,"db_name":"cust_6dd1d906_pg_webapp_prod_db","username":"cust_6dd1d906_pg_webapp_prod_db","password":"Xk9mN2pQ4rS7tV3wYz","host":"db.no.wayscloud.services","port":5432,"connection_string":"postgresql://cust_6dd1d906_pg_webapp_prod_db:Xk9mN2pQ4rS7tV3wYz@db.no.wayscloud.services:5432/cust_6dd1d906_pg_webapp_prod_db","created_at":"2025-10-15T14:30:00Z"}},"DatabaseDeleteRequest":{"properties":{"db_type":{"type":"string","enum":["postgresql","mariadb"],"title":"Db Type"},"db_name":{"type":"string","title":"Db Name"},"username":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Username"}},"type":"object","required":["db_type","db_name"],"title":"DatabaseDeleteRequest","description":"Delete a database permanently.\n\n**Warning**: This action is irreversible. All data will be permanently deleted.\nMake sure you have backups before proceeding.","example":{"db_type":"postgresql","db_name":"cust_6dd1d906_pg_webapp_prod_db","username":"cust_6dd1d906_pg_webapp_prod_db"}},"DatabaseDeleteResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","message"],"title":"DatabaseDeleteResponse","description":"Confirmation that your database was deleted.\n\nIf successful, the database and all its data have been permanently removed.","example":{"success":true,"message":"Database 'cust_6dd1d906_pg_webapp_prod_db' deleted successfully"}},"DatabaseInfoResponse":{"properties":{"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"size_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Size Mb"},"connection_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Connection Count"},"connection_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Connection Limit"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"}},"type":"object","required":["db_name","db_type"],"title":"DatabaseInfoResponse","description":"Detailed information about your database.\n\nShows current storage usage, active connections, and other metrics\nto help you monitor and plan capacity.","example":{"db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":256.5,"connection_count":12,"connection_limit":100,"created_at":"2025-10-15T14:30:00Z","owner":"cust_6dd1d906_pg_webapp_prod_db"}},"DatabaseListResponse":{"properties":{"db_type":{"type":"string","title":"Db Type"},"databases":{"items":{"type":"string"},"type":"array","title":"Databases"}},"type":"object","required":["db_type","databases"],"title":"DatabaseListResponse","description":"Your databases of a specific type.\n\nReturns the database names you own. To get detailed information about a \nspecific database (size, connections, etc.), use the `/info` endpoint.","example":{"db_type":"postgresql","databases":["cust_6dd1d906_pg_webapp_prod_db","cust_6dd1d906_pg_analytics_db","cust_6dd1d906_pg_staging_db"]}},"DatabaseScaleRequest":{"properties":{"connection_limit":{"anyOf":[{"type":"integer","maximum":1000.0,"minimum":10.0},{"type":"null"}],"title":"Connection Limit","description":"Maximum connections allowed (10-1000)"},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description","description":"Optional description/notes for this database"}},"type":"object","title":"DatabaseScaleRequest","description":"Request to scale database resources","example":{"connection_limit":200,"description":"Increased connection limit for production traffic"}},"FirewallAddRequest":{"properties":{"ip_address":{"type":"string","title":"Ip Address","description":"IP address or CIDR block to whitelist"},"description":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Description","description":"Optional rule description (e.g., 'Office network')"}},"type":"object","required":["ip_address"],"title":"FirewallAddRequest","description":"Add an IP address to your database's firewall whitelist.\n\nSimpler than FirewallAllowRequest - the database is specified in the URL path,\nso you only need to provide the IP address.","example":{"ip_address":"192.0.2.100/32","description":"Office VPN connection"}},"FirewallListResponse":{"properties":{"rules":{"items":{"$ref":"#/components/schemas/FirewallRule"},"type":"array","title":"Rules"},"total":{"type":"integer","title":"Total","description":"Total number of rules"}},"type":"object","required":["rules","total"],"title":"FirewallListResponse","description":"All firewall rules for your databases.\n\nShows which IP addresses are currently allowed to connect to each of your databases.","example":{"rules":[{"rule_id":"fw-a1b2c3d4-5678-90ab-cdef-123456789abc","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","ip_address":"192.0.2.100/32","port":5432,"description":"Office VPN connection","created_at":"2025-10-20T09:15:00Z"},{"rule_id":"fw-b2c3d4e5-6789-01bc-def0-234567890bcd","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","ip_address":"203.0.113.50/32","port":5432,"description":"Home office","created_at":"2025-10-22T14:30:00Z"}],"total":2}},"FirewallRule":{"properties":{"rule_id":{"type":"string","title":"Rule Id","description":"Unique rule identifier (UUID)"},"db_name":{"type":"string","title":"Db Name","description":"Database name"},"db_type":{"type":"string","title":"Db Type","description":"Database type"},"ip_address":{"type":"string","title":"Ip Address","description":"IP address or CIDR block"},"port":{"type":"integer","title":"Port","description":"Database port"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional rule description"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["rule_id","db_name","db_type","ip_address","port","created_at"],"title":"FirewallRule","description":"A firewall rule that allows access to your database.\n\nEach rule whitelists a specific IP address or CIDR block to connect to your database.\nUse the `rule_id` to delete this rule later.","example":{"rule_id":"fw-a1b2c3d4-5678-90ab-cdef-123456789abc","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","ip_address":"192.0.2.100/32","port":5432,"description":"Office VPN connection","created_at":"2025-10-20T09:15:00Z"}},"FirewallRuleResponse":{"properties":{"rule_id":{"type":"string","title":"Rule Id","description":"Unique rule identifier (UUID)"},"db_name":{"type":"string","title":"Db Name","description":"Database name"},"db_type":{"type":"string","title":"Db Type","description":"Database type"},"ip_address":{"type":"string","title":"Ip Address","description":"IP address or CIDR block"},"port":{"type":"integer","title":"Port","description":"Database port"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional rule description"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["rule_id","db_name","db_type","ip_address","port","created_at"],"title":"FirewallRuleResponse","description":"Details of a firewall rule that was created or retrieved.\n\nThe `rule_id` uniquely identifies this rule and is needed to delete it later.","example":{"rule_id":"fw-a1b2c3d4-5678-90ab-cdef-123456789abc","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","ip_address":"192.0.2.100/32","port":5432,"description":"Office VPN connection","created_at":"2025-10-20T09:15:00Z"}},"SnapshotCreateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Name","description":"Short name for the snapshot (max 80 chars)"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description","description":"Longer description (max 500 chars)"}},"type":"object","title":"SnapshotCreateRequest","description":"Create a point-in-time snapshot of your database.\n\nSnapshots capture the current state of your database and can be used\nto restore data if needed. Add a description to remember why you created it.","example":{"description":"Before major schema migration"}},"SnapshotCreateResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"snapshot_id":{"type":"string","title":"Snapshot Id"},"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"size_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Size Mb"},"created_at":{"type":"string","title":"Created At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"message":{"type":"string","title":"Message"},"s3_backup":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"S3 Backup"},"offsite_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offsite Status"}},"type":"object","required":["success","snapshot_id","db_name","db_type","created_at","message"],"title":"SnapshotCreateResponse","description":"Your snapshot was created successfully.\n\nSave the `snapshot_id` - you'll need it to restore from this snapshot later.\nSnapshots are retained according to your backup policy.","example":{"success":true,"snapshot_id":"snap_20251126_143000_a1b2c3d4","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":256.5,"created_at":"2025-11-26T14:30:00Z","description":"Before major schema migration","message":"Snapshot created successfully"}},"SnapshotInfo":{"properties":{"snapshot_id":{"type":"string","title":"Snapshot Id"},"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"size_mb":{"type":"number","title":"Size Mb"},"created_at":{"type":"string","title":"Created At"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"status":{"type":"string","title":"Status"},"location":{"type":"string","title":"Location","default":"local"},"snapshot_type":{"type":"string","title":"Snapshot Type","default":"manual"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"offsite_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Offsite Status"}},"type":"object","required":["snapshot_id","db_name","db_type","size_mb","created_at","status"],"title":"SnapshotInfo","description":"Information about a database snapshot.\n\nShows when the snapshot was taken, its size, and current status.\nUse the `snapshot_id` to restore from this snapshot.","example":{"snapshot_id":"snap_20251126_143000_a1b2c3d4","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":256.5,"created_at":"2025-11-26T14:30:00Z","description":"Before major schema migration","status":"completed"}},"SnapshotListResponse":{"properties":{"db_name":{"type":"string","title":"Db Name"},"db_type":{"type":"string","title":"Db Type"},"snapshots":{"items":{"$ref":"#/components/schemas/SnapshotInfo"},"type":"array","title":"Snapshots"}},"type":"object","required":["db_name","db_type","snapshots"],"title":"SnapshotListResponse","description":"All snapshots for a specific database.\n\nLists both manual snapshots you've created and automatic backups\nfrom your backup policy.","example":{"db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","snapshots":[{"snapshot_id":"snap_20251126_143000_a1b2c3d4","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":256.5,"created_at":"2025-11-26T14:30:00Z","description":"Before major schema migration","status":"completed"},{"snapshot_id":"snap_20251125_020015_b2c3d4e5","db_name":"cust_6dd1d906_pg_webapp_prod_db","db_type":"postgresql","size_mb":248.2,"created_at":"2025-11-25T02:00:15Z","description":"Daily automated backup","status":"completed"}]}},"SnapshotRestoreRequest":{"properties":{"snapshot_id":{"type":"string","title":"Snapshot Id","description":"Snapshot ID to restore from"},"target_db_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Target Db Name","description":"Optional: Restore to new database name"}},"type":"object","required":["snapshot_id"],"title":"SnapshotRestoreRequest","description":"Restore your database from a snapshot.\n\nYou can either restore to the original database (overwrites current data)\nor create a new database from the snapshot by specifying `target_db_name`.","example":{"snapshot_id":"snap_20251126_143000_a1b2c3d4","target_db_name":"webapp_restored_db"}},"SnapshotRestoreResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"db_name":{"type":"string","title":"Db Name"},"snapshot_id":{"type":"string","title":"Snapshot Id"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","db_name","snapshot_id","message"],"title":"SnapshotRestoreResponse","description":"Your database was restored from the snapshot.\n\nIf you specified a `target_db_name`, a new database was created with the\nrestored data. Otherwise, the original database was overwritten.","example":{"success":true,"db_name":"cust_6dd1d906_pg_webapp_restored_db","snapshot_id":"snap_20251126_143000_a1b2c3d4","message":"Database restored successfully from snapshot"}},"ChatChoice":{"properties":{"index":{"type":"integer","title":"Index"},"message":{"$ref":"#/components/schemas/ResponseMessage"},"finish_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Finish Reason"}},"type":"object","required":["index","message"],"title":"ChatChoice","description":"A single response from the model.\n\nMost requests return one choice. The `finish_reason` tells you why the\nmodel stopped: 'stop' means it finished naturally, 'length' means it\nhit the max_tokens limit.","example":{"index":0,"message":{"role":"assistant","content":"Hello! I'm doing well, thank you for asking. How can I assist you today?"},"finish_reason":"stop"}},"ChatRequest":{"properties":{"model":{"type":"string","title":"Model","description":"Model alias (e.g., 'mixtral-8x7b')"},"messages":{"items":{"$ref":"#/components/schemas/Message"},"type":"array","minItems":1,"title":"Messages"},"stream":{"type":"boolean","title":"Stream","description":"Enable SSE streaming","default":false},"temperature":{"anyOf":[{"type":"number","maximum":2.0,"minimum":0.0},{"type":"null"}],"title":"Temperature"},"max_tokens":{"anyOf":[{"type":"integer","exclusiveMinimum":0.0},{"type":"null"}],"title":"Max Tokens"},"top_p":{"anyOf":[{"type":"number","maximum":1.0,"minimum":0.0},{"type":"null"}],"title":"Top P"},"tools":{"anyOf":[{"items":{"type":"object"},"type":"array"},{"type":"null"}],"title":"Tools","description":"Tool definitions for function calling. Accepted for OpenAI compatibility, not yet implemented."},"tool_choice":{"anyOf":[{"type":"string"},{"type":"object"},{"type":"null"}],"title":"Tool Choice","description":"Controls tool selection. Accepted for OpenAI compatibility, not yet implemented."},"agent_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Id","description":"Agent identifier for AI agents. Stored for logging only."},"region":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Region","description":"Preferred datacenter region for inference. Currently only 'oslo' is available."}},"additionalProperties":false,"type":"object","required":["model","messages"],"title":"ChatRequest","description":"Send a prompt to an AI model.\n\nThis endpoint is **OpenAI-compatible** - if you're already using the OpenAI SDK,\njust change the base URL to `https://llm.wayscloud.services` and it will work.\n\n**Quick start**: Set `model` to one of our available models (see `/v1/models`)\nand provide your messages. That's it!\n\n**Streaming**: Set `stream: true` for real-time responses (recommended for chat interfaces).","examples":[{"max_tokens":100,"messages":[{"content":"You are a helpful assistant.","role":"system"},{"content":"Hello!","role":"user"}],"model":"mixtral-8x7b","temperature":0.7},{"max_tokens":500,"messages":[{"content":"Explain quantum computing","role":"user"}],"model":"qwen3-235b-instruct","stream":true},{"agent_id":"ephemeral","max_tokens":1000,"messages":[{"content":"Write a Python function","role":"user"}],"model":"deepseek-v3","temperature":0.3,"tools":[]}],"example":{"model":"qwen3-235b-thinking","messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"What is the capital of Norway?"}],"temperature":0.7,"max_tokens":256}},"ChatResponse":{"properties":{"model_output_metadata":{"$ref":"#/components/schemas/ReasoningContainer","description":"Optional reasoning metadata returned by reasoning-capable models"}},"type":"object","required":["id","created","model","choices","usage"],"title":"ChatResponse","description":"The AI's response to your prompt.\n\nThe response includes the generated text in `choices[0].message.content`,\nalong with token usage for billing. Save the `id` if you need to reference\nthis request later.","example":{"id":"chatcmpl-abc123","object":"chat.completion","created":1700000000,"model":"deepseek-r1","choices":[{"index":0,"message":{"role":"assistant","content":"The capital of Norway is Oslo."},"finish_reason":"stop"}],"usage":{"prompt_tokens":15,"completion_tokens":8,"total_tokens":23},"model_output_metadata":{"reasoning":{"summary":"Model evaluated alternative interpretations and selected the most likely answer.","depth":"standard","steps":["Parsed user intent","Evaluated context","Performed logical deduction","Selected final response"],"tokens":214}}}},"ContentPart":{"properties":{"type":{"type":"string","enum":["text","image_url"],"title":"Type","description":"Content type"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text","description":"Text content (when type='text')"},"image_url":{"anyOf":[{"$ref":"#/components/schemas/ImageUrl"},{"type":"null"}],"description":"Image URL (when type='image_url')"}},"type":"object","required":["type"],"title":"ContentPart","description":"A single part of multimodal content"},"ErrorResponse":{"properties":{"error":{"type":"object","title":"Error"}},"type":"object","required":["error"],"title":"ErrorResponse","description":"Error details when something goes wrong.\n\nCheck `error.message` for a human-readable explanation and `error.code`\nfor programmatic handling.","example":{"error":{"message":"Model 'invalid-model' not found. Available models: mixtral-8x7b, deepseek-v3","type":"invalid_request_error","code":"model_not_found"}}},"ImageUrl":{"properties":{"url":{"type":"string","title":"Url","description":"Image URL or base64 data URI (data:image/png;base64,...)"},"detail":{"anyOf":[{"type":"string","enum":["auto","low","high"]},{"type":"null"}],"title":"Detail","description":"Image detail level for vision processing","default":"auto"}},"type":"object","required":["url"],"title":"ImageUrl","description":"Image URL for vision models"},"Message":{"properties":{"role":{"type":"string","enum":["system","user","assistant"],"title":"Role","description":"Message role: 'system' for instructions, 'user' for user input, 'assistant' for AI responses"},"content":{"anyOf":[{"type":"string"},{"items":{"$ref":"#/components/schemas/ContentPart"},"type":"array"}],"title":"Content","description":"Message content - string or list of content parts for multimodal"}},"type":"object","required":["role","content"],"title":"Message","description":"A single message in the conversation.\n\nBuild your conversation by adding messages with different roles:\n- `system`: Set the AI's behavior and personality\n- `user`: Your questions or prompts\n- `assistant`: Previous AI responses (for multi-turn conversations)\n\nFor vision models (e.g., qwen3-vl-8b), content can be a list of parts:\n```json\n{\n  \"role\": \"user\",\n  \"content\": [\n    {\"type\": \"text\", \"text\": \"What's in this image?\"},\n    {\"type\": \"image_url\", \"image_url\": {\"url\": \"data:image/png;base64,...\"}}\n  ]\n}\n```","examples":[{"content":"Hello, how are you?","role":"user"},{"content":[{"text":"What's in this image?","type":"text"},{"image_url":{"url":"data:image/jpeg;base64,/9j/4AAQ..."},"type":"image_url"}],"role":"user"}]},"ModelOutputMetadata":{"properties":{"reasoning":{"anyOf":[{"$ref":"#/components/schemas/ReasoningMetadata"},{"type":"null"}],"description":"Reasoning metadata from chain-of-thought models"}},"type":"object","title":"ModelOutputMetadata","description":"Extended metadata returned by certain model types.\n\nCurrently includes reasoning traces from reasoning-capable models."},"ResponseMessage":{"properties":{"role":{"const":"assistant","title":"Role","default":"assistant"},"content":{"type":"string","title":"Content","description":"Response text content"}},"type":"object","required":["content"],"title":"ResponseMessage","description":"Response message - always has string content"},"Usage":{"properties":{"prompt_tokens":{"type":"integer","title":"Prompt Tokens","description":"Tokens in your input messages"},"completion_tokens":{"type":"integer","title":"Completion Tokens","description":"Tokens in the AI's response"},"total_tokens":{"type":"integer","title":"Total Tokens","description":"Total tokens (prompt + completion)"}},"type":"object","required":["prompt_tokens","completion_tokens","total_tokens"],"title":"Usage","description":"Token usage for this request.\n\nTokens are the basic unit of text that models process. Billing is based on\ntotal tokens used. Check `/v1/models` for per-token pricing.","example":{"prompt_tokens":25,"completion_tokens":42,"total_tokens":67}},"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["person","company"]},"name":{"type":"string"},"company_name":{"type":"string","nullable":true},"phone_number":{"type":"string","nullable":true},"email":{"type":"string","format":"email","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"notes":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"example":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","type":"person","name":"Ola Nordmann","company_name":"ACME AS","phone_number":"+4799999999","email":"ola@example.com","tags":["vip","newsletter"],"notes":"Key contact for project X","created_at":"2025-01-15T09:30:00Z","updated_at":"2025-01-15T09:30:00Z"}},"ContactCreate":{"type":"object","required":["name"],"properties":{"type":{"type":"string","enum":["person","company"],"default":"person"},"name":{"type":"string","minLength":1,"maxLength":255},"company_name":{"type":"string","maxLength":255},"phone_number":{"type":"string","maxLength":30,"description":"E.164 format recommended"},"email":{"type":"string","format":"email"},"tags":{"type":"array","items":{"type":"string"}},"notes":{"type":"string"}},"example":{"type":"person","name":"Ola Nordmann","phone_number":"+4799999999","email":"ola@example.com","tags":["vip"]}},"ContactGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"member_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-1234-5678-9abc-def012345678","name":"Newsletter Subscribers","description":"Customers who opted in for newsletter","member_count":150,"created_at":"2025-01-15T09:30:00Z"}},"ContactGroupCreate":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100},"description":{"type":"string","maxLength":500}},"example":{"name":"VIP Customers","description":"High-value customers for priority support"}},"AlarmActionRequest":{"properties":{"note":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Note","description":"Optional note","example":"Investigating root cause"}},"type":"object","title":"AlarmActionRequest","description":"Acknowledge, resolve, or act on an alarm"},"ChannelCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Channel name","example":"Ops Slack"},"channel_type":{"type":"string","title":"Channel Type","description":"Channel type: email, webhook, slack, teams, sms","example":"webhook"},"config":{"additionalProperties":true,"type":"object","title":"Config","description":"Channel-type-specific configuration","example":{"secret":"my-hmac-secret","url":"https://example.com/webhook"}},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether channel is active","default":true}},"type":"object","required":["name","channel_type","config"],"title":"ChannelCreateRequest","description":"Create a notification channel"},"ChannelUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled"}},"type":"object","title":"ChannelUpdateRequest","description":"Update a notification channel"},"DatapointCreateRequest":{"properties":{"key":{"type":"string","maxLength":100,"minLength":1,"title":"Key","description":"Datapoint key (unique per customer)","example":"temperature"},"label":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Label","description":"Human-readable label","example":"Temperature"},"unit":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Unit","description":"Measurement unit","example":"°C"},"data_type":{"type":"string","title":"Data Type","description":"Data type: number, string, boolean","default":"number","example":"number"}},"type":"object","required":["key"],"title":"DatapointCreateRequest","description":"Define a named telemetry datapoint"},"DatapointUpdateRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Label"},"unit":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Unit"}},"type":"object","title":"DatapointUpdateRequest","description":"Update a datapoint definition"},"DeviceCreateRequest":{"properties":{"device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id","description":"Unique device identifier. Auto-generated UUID if omitted.","example":"sensor-001"},"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Human-readable device name","example":"Temperature Sensor A"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"Device description","example":"Office floor 3, room 301"},"device_type":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Device Type","description":"Device type or model","example":"ESP32-TempHumidity"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Custom key-value metadata","example":{"firmware":"1.2.0","location":"oslo"}}},"type":"object","required":["name"],"title":"DeviceCreateRequest","description":"Register a new IoT device"},"DeviceListResponse":{"properties":{"devices":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Devices","description":"List of device objects"},"total":{"type":"integer","title":"Total","description":"Total number of devices matching filter","example":42},"page":{"type":"integer","title":"Page","description":"Current page number","example":1},"page_size":{"type":"integer","title":"Page Size","description":"Items per page","example":20}},"type":"object","required":["devices","total","page","page_size"],"title":"DeviceListResponse","description":"Paginated list of devices"},"DeviceUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"New device name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"New description"},"device_type":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Device Type","description":"New device type"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Replace custom metadata"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active","description":"Activate or deactivate device"}},"type":"object","title":"DeviceUpdateRequest","description":"Update device information"},"GroupCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Group name","example":"Factory Floor A"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"Group description"}},"type":"object","required":["name"],"title":"GroupCreateRequest","description":"Create a device group"},"GroupUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"New group name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"New description"}},"type":"object","title":"GroupUpdateRequest","description":"Update a device group"},"PolicyCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Policy name","example":"Critical to Ops"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"severity_filter":{"items":{"type":"string"},"type":"array","title":"Severity Filter","description":"Which alarm severities to match","default":["critical","warning","info"]},"rule_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Rule Ids","description":"Specific rule IDs to match (null = all rules)"},"scope_type":{"type":"string","title":"Scope Type","description":"Scope: fleet, device, group, profile","default":"fleet"},"scope_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Scope Ids","description":"IDs within scope (null for fleet)"},"event_types":{"items":{"type":"string"},"type":"array","title":"Event Types","description":"Alarm events: triggered, acknowledged, resolved","default":["triggered"]},"channel_ids":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Channel Ids","description":"Channel IDs to deliver to"},"cooldown_seconds":{"type":"integer","maximum":86400.0,"minimum":0.0,"title":"Cooldown Seconds","description":"Minimum seconds between notifications for same alarm","default":300},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether policy is active","default":true}},"type":"object","required":["name","channel_ids"],"title":"PolicyCreateRequest","description":"Create a notification policy"},"PolicyUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"severity_filter":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Severity Filter"},"event_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Event Types"},"channel_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Channel Ids"},"cooldown_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":0.0},{"type":"null"}],"title":"Cooldown Seconds"},"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled"}},"type":"object","title":"PolicyUpdateRequest","description":"Update a notification policy"},"ProfileCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Profile name","example":"Default Sensor Config"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"Profile description"},"expected_topics":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Expected Topics","description":"MQTT topics the device should publish to","example":["sensors/temperature","sensors/humidity"]},"reporting_interval_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":1.0},{"type":"null"}],"title":"Reporting Interval Seconds","description":"Expected reporting interval in seconds","example":60},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata","description":"Profile metadata"}},"type":"object","required":["name"],"title":"ProfileCreateRequest","description":"Create a device profile (configuration template)"},"ProfileUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description"},"expected_topics":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Expected Topics"},"reporting_interval_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":1.0},{"type":"null"}],"title":"Reporting Interval Seconds"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","title":"ProfileUpdateRequest","description":"Update a device profile"},"RuleCreateRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Rule name","example":"Offline Alert"},"description":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Description","description":"Rule description"},"rule_type":{"type":"string","title":"Rule Type","description":"Rule type: missing_data, offline, threshold, message_rate, reconnect_rate","example":"offline"},"scope_type":{"type":"string","title":"Scope Type","description":"Scope: fleet, group, profile, device","default":"fleet","example":"fleet"},"scope_device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope Device Id","description":"Device ID when scope_type=device"},"scope_group_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope Group Id","description":"Group ID when scope_type=group"},"scope_profile_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope Profile Id","description":"Profile ID when scope_type=profile"},"config":{"additionalProperties":true,"type":"object","title":"Config","description":"Rule-type-specific configuration","example":{"silence_seconds":300}},"severity":{"type":"string","title":"Severity","description":"Alarm severity: critical, warning, info","default":"warning","example":"critical"},"actions":{"items":{"type":"string"},"type":"array","title":"Actions","description":"Actions on trigger: create_alarm, send_webhook, mark_unhealthy","default":["create_alarm"]},"cooldown_seconds":{"type":"integer","maximum":86400.0,"minimum":60.0,"title":"Cooldown Seconds","description":"Minimum seconds between alarm triggers","default":300,"example":300},"is_enabled":{"type":"boolean","title":"Is Enabled","description":"Whether rule is active","default":true},"auto_resolve":{"type":"boolean","title":"Auto Resolve","description":"Auto-resolve alarm when condition clears","default":false}},"type":"object","required":["name","rule_type","config"],"title":"RuleCreateRequest","description":"Create an alarm rule"},"RuleUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"config":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Config"},"severity":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Severity"},"actions":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Actions"},"cooldown_seconds":{"anyOf":[{"type":"integer","maximum":86400.0,"minimum":60.0},{"type":"null"}],"title":"Cooldown Seconds"},"is_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Enabled"},"auto_resolve":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Auto Resolve"}},"type":"object","title":"RuleUpdateRequest","description":"Update an alarm rule"},"IoTDevice":{"type":"object","properties":{"device_id":{"type":"string","description":"Unique device identifier"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"device_type":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"is_online":{"type":"boolean"},"last_seen":{"type":"string","format":"date-time","nullable":true},"connection_status":{"type":"string","enum":["connected","disconnected"]},"stability_score":{"type":"integer","nullable":true},"firmware_version":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"metadata":{"type":"object","nullable":true},"messages_24h":{"type":"integer","nullable":true},"battery_level":{"type":"integer","nullable":true},"signal_strength":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"device_id":"sensor-001","name":"Temperature Sensor A","device_type":"ESP32","is_active":true,"is_online":true,"last_seen":"2026-03-30T09:55:00Z","connection_status":"connected","stability_score":95,"firmware_version":"2.1.0","tags":["production","floor-3"],"metadata":{"location":"Building A"},"messages_24h":1440,"battery_level":87,"signal_strength":-65,"created_at":"2026-01-15T10:00:00Z"}},"IoTDeviceList":{"type":"object","properties":{"devices":{"type":"array","items":{"$ref":"#/components/schemas/IoTDevice"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"},"has_more":{"type":"boolean"}},"example":{"devices":[{"device_id":"sensor-001","name":"Temperature Sensor A","is_active":true,"is_online":true,"last_seen":"2026-03-30T09:55:00Z"},{"device_id":"sensor-002","name":"Humidity Sensor B","is_active":true,"is_online":true}],"total":42,"page":1,"page_size":20,"has_more":true}},"IoTDeviceHealth":{"type":"object","properties":{"device_id":{"type":"string"},"health_score":{"type":"integer","minimum":0,"maximum":100},"health_status":{"type":"string","enum":["healthy","degraded","unhealthy","offline","unknown"]},"components":{"type":"object"},"checked_at":{"type":"string","format":"date-time"}},"example":{"device_id":"sensor-001","health_score":92,"health_status":"healthy","components":{"uptime":100,"alarm_burden":95,"connection_stability":90,"telemetry_freshness":85},"checked_at":"2026-03-30T10:00:00Z"}},"IoTDeviceTags":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"example":{"tags":["production","floor-3","critical"]}},"IoTDeviceTagsAdded":{"type":"object","properties":{"added":{"type":"array","items":{"type":"string"}}},"example":{"added":["new-tag-1","new-tag-2"]}},"IoTGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"color":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"example":{"id":"a1b2c3d4-5678-9abc-def0-123456789abc","name":"Floor 3 Sensors","description":"All sensors on floor 3","color":"#FF5733","created_at":"2026-01-20T14:00:00Z","updated_at":"2026-03-20T14:22:00Z"}},"IoTGroupList":{"type":"object","properties":{"groups":{"type":"array","items":{"$ref":"#/components/schemas/IoTGroup"}}},"example":{"groups":[{"id":"a1b2c3d4-5678-9abc-def0-123456789abc","name":"Floor 3 Sensors","description":"All sensors on floor 3","color":"#FF5733"}]}},"IoTGroupDevicesAdded":{"type":"object","properties":{"count":{"type":"integer"},"added":{"type":"array","items":{"type":"string"}}},"example":{"count":3,"added":["sensor-001","sensor-002","sensor-003"]}},"IoTProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"expected_topics":{"type":"array","items":{"type":"string"},"nullable":true},"reporting_interval_seconds":{"type":"integer","nullable":true},"metadata":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"example":{"id":"b2c3d4e5-6789-0abc-def1-234567890bcd","name":"Temperature Sensor Profile","description":"Standard temperature/humidity sensors","expected_topics":["devices/+/sensors/temperature","devices/+/sensors/humidity"],"reporting_interval_seconds":60,"metadata":{"sensor_type":"DHT22"},"created_at":"2026-01-10T12:00:00Z","updated_at":"2026-03-20T14:22:00Z"}},"IoTProfileList":{"type":"object","properties":{"profiles":{"type":"array","items":{"$ref":"#/components/schemas/IoTProfile"}}},"example":{"profiles":[{"id":"b2c3d4e5-6789-0abc-def1-234567890bcd","name":"Temperature Sensor Profile","reporting_interval_seconds":60}]}},"IoTProfileApplyResult":{"type":"object","properties":{"profile_id":{"type":"string"},"device_id":{"type":"string"},"applied":{"type":"boolean"}},"example":{"profile_id":"prof-abc123","device_id":"sensor-001","applied":true}},"IoTRule":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"rule_type":{"type":"string","enum":["offline","missing_data","threshold","message_rate","reconnect_rate"]},"scope_type":{"type":"string","enum":["fleet","device","group","profile"]},"scope_device_id":{"type":"string","nullable":true},"scope_group_id":{"type":"string","nullable":true},"scope_profile_id":{"type":"string","nullable":true},"config":{"type":"object"},"severity":{"type":"string","enum":["critical","warning","info"]},"actions":{"type":"array","items":{"type":"string"}},"cooldown_seconds":{"type":"integer"},"is_enabled":{"type":"boolean"},"auto_resolve":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"last_evaluated_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"c3d4e5f6-7890-1abc-def2-345678901cde","name":"High temperature alert","rule_type":"threshold","scope_type":"fleet","config":{"datapoint_id":"dp-abc123","operator":"gt","value":35,"duration_seconds":300},"severity":"warning","actions":["create_alarm"],"cooldown_seconds":300,"is_enabled":true,"auto_resolve":false,"created_at":"2026-02-01T10:00:00Z","updated_at":"2026-03-20T14:22:00Z"}},"IoTRuleList":{"type":"object","properties":{"rules":{"type":"array","items":{"$ref":"#/components/schemas/IoTRule"}}},"example":{"rules":[{"id":"c3d4e5f6-7890-1abc-def2-345678901cde","name":"High temperature alert","rule_type":"threshold","severity":"warning","is_enabled":true}]}},"IoTAlarm":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"device_id":{"type":"string"},"rule_id":{"type":"string","format":"uuid"},"rule_name":{"type":"string","nullable":true},"severity":{"type":"string","enum":["critical","warning","info"]},"title":{"type":"string"},"message":{"type":"string"},"status":{"type":"string","enum":["active","acknowledged","resolved","suppressed","expired"]},"triggered_at":{"type":"string","format":"date-time"},"occurrence_count":{"type":"integer"},"acknowledged_at":{"type":"string","format":"date-time","nullable":true},"acknowledged_by":{"type":"string","nullable":true},"resolved_at":{"type":"string","format":"date-time","nullable":true},"resolution_note":{"type":"string","nullable":true},"context":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"d4e5f6a7-8901-2bcd-ef34-567890123def","device_id":"sensor-001","rule_id":"c3d4e5f6-7890-1abc-def2-345678901cde","rule_name":"High temperature alert","severity":"warning","title":"Device offline: sensor-001","message":"Device has been offline for 5 minutes","status":"active","triggered_at":"2026-03-30T14:00:00Z","occurrence_count":3,"acknowledged_at":null,"resolved_at":null,"created_at":"2026-03-30T14:00:00Z"}},"IoTAlarmList":{"type":"object","properties":{"alarms":{"type":"array","items":{"$ref":"#/components/schemas/IoTAlarm"}},"total":{"type":"integer"},"page":{"type":"integer"},"page_size":{"type":"integer"}},"example":{"alarms":[{"id":"d4e5f6a7-8901-2bcd-ef34-567890123def","device_id":"sensor-001","severity":"warning","title":"Device offline: sensor-001","status":"active","triggered_at":"2026-03-30T14:00:00Z","occurrence_count":3}],"total":12,"page":1,"page_size":50}},"IoTAlarmSummary":{"type":"object","properties":{"by_status":{"type":"object"},"by_severity":{"type":"object"},"active_total":{"type":"integer"}},"example":{"by_status":{"active":8,"acknowledged":3,"resolved":45},"by_severity":{"critical":2,"warning":7,"info":3},"active_total":11}},"IoTAlarmTimeline":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"event_type":{"type":"string"},"actor":{"type":"string"},"note":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"example":[{"id":1,"event_type":"triggered","actor":"rule_engine","note":null,"metadata":{"severity":"warning"},"created_at":"2026-03-30T14:00:00Z"},{"id":2,"event_type":"acknowledged","actor":"operator@example.com","note":"Investigating","metadata":null,"created_at":"2026-03-30T14:15:00Z"}]},"IoTDatapoint":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"label":{"type":"string","nullable":true},"source_topic":{"type":"string","nullable":true},"json_path":{"type":"string","nullable":true},"data_type":{"type":"string","enum":["number","string","boolean"]},"unit":{"type":"string","nullable":true},"aggregation_method":{"type":"string","nullable":true},"is_chartable":{"type":"boolean"},"is_alertable":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}},"example":{"id":"e5f6a7b8-9012-3cde-f456-7890123456ef","name":"temperature","label":"Temperature","unit":"°C","data_type":"number","aggregation_method":"avg","is_chartable":true,"is_alertable":true,"created_at":"2026-01-10T12:00:00Z"}},"IoTDatapointList":{"type":"object","properties":{"datapoints":{"type":"array","items":{"$ref":"#/components/schemas/IoTDatapoint"}}},"example":{"datapoints":[{"id":"e5f6a7b8-9012-3cde-f456-7890123456ef","name":"temperature","label":"Temperature","unit":"°C","data_type":"number"}]}},"IoTTelemetryData":{"type":"array","items":{"type":"object","properties":{"datapoint_id":{"type":"string"},"name":{"type":"string"},"label":{"type":"string","nullable":true},"unit":{"type":"string","nullable":true},"data_type":{"type":"string"},"measured_at":{"type":"string","format":"date-time"},"value":{}}},"example":[{"datapoint_id":"dp-abc123","name":"temperature","label":"Temperature","unit":"°C","data_type":"number","measured_at":"2026-03-30T09:55:00Z","value":22.5},{"datapoint_id":"dp-def456","name":"humidity","label":"Humidity","unit":"%","data_type":"number","measured_at":"2026-03-30T09:55:00Z","value":45.2}]},"IoTChannel":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"channel_type":{"type":"string","enum":["email","webhook","slack","teams","sms"]},"config":{"type":"object"},"is_enabled":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"f6a7b8c9-0123-4def-5678-901234567890","name":"Ops Webhook","channel_type":"webhook","config":{"url":"https://example.com/webhooks/iot"},"is_enabled":true,"created_at":"2026-02-01T10:00:00Z"}},"IoTChannelList":{"type":"object","properties":{"channels":{"type":"array","items":{"$ref":"#/components/schemas/IoTChannel"}}},"example":{"channels":[{"id":"f6a7b8c9-0123-4def-5678-901234567890","name":"Ops Webhook","channel_type":"webhook","is_enabled":true}]}},"IoTChannelTestResult":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"channel_id":{"type":"string"}},"example":{"success":true,"message":"Test notification sent successfully","channel_id":"ch-abc123"}},"IoTPolicy":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"severity_filter":{"type":"array","items":{"type":"string"}},"event_types":{"type":"array","items":{"type":"string"}},"channel_ids":{"type":"array","items":{"type":"string"}},"cooldown_seconds":{"type":"integer"},"is_enabled":{"type":"boolean"},"created_at":{"type":"string","format":"date-time","nullable":true}},"example":{"id":"a7b8c9d0-1234-5ef6-7890-123456789012","name":"Critical alerts to Slack","severity_filter":["critical"],"event_types":["triggered"],"channel_ids":["ch-abc123"],"cooldown_seconds":300,"is_enabled":true,"created_at":"2026-02-01T10:00:00Z"}},"IoTPolicyList":{"type":"object","properties":{"policies":{"type":"array","items":{"$ref":"#/components/schemas/IoTPolicy"}}},"example":{"policies":[{"id":"a7b8c9d0-1234-5ef6-7890-123456789012","name":"Critical alerts to Slack","severity_filter":["critical"],"is_enabled":true}]}},"IoTDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"channel_type":{"type":"string"},"status":{"type":"string","enum":["queued","delivered","failed"]},"alarm_id":{"type":"string","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"error_message":{"type":"string","nullable":true}},"example":{"id":"b8c9d0e1-2345-6f78-9012-345678901234","channel_type":"webhook","status":"delivered","alarm_id":"alm-abc123","sent_at":"2026-03-30T14:05:00Z","error_message":null}},"IoTDeliveryList":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/IoTDelivery"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}},"example":{"deliveries":[{"id":"b8c9d0e1-2345-6f78-9012-345678901234","channel_type":"webhook","status":"delivered","sent_at":"2026-03-30T14:05:00Z"}],"total":25,"limit":50,"offset":0}},"IoTCredentials":{"type":"object","properties":{"mqtt_broker":{"type":"object","properties":{"host":{"type":"string"},"port":{"type":"integer"},"tls_port":{"type":"integer"},"websocket_port":{"type":"integer"},"protocol":{"type":"string"}}}},"example":{"mqtt_broker":{"host":"mqtt.wayscloud.services","port":1883,"tls_port":8883,"websocket_port":8083,"protocol":"mqtt"}}},"IoTSubscription":{"type":"object","properties":{"plan":{"type":"string","nullable":true},"status":{"type":"string","enum":["active","inactive","suspended"]},"device_limit":{"type":"integer"},"device_count":{"type":"integer","nullable":true},"created_at":{"type":"string","format":"date-time","nullable":true}},"example":{"plan":"iot-50","status":"active","device_limit":50,"device_count":12,"created_at":"2026-01-01T00:00:00Z"}},"IoTUsage":{"type":"object","properties":{"messages_today":{"type":"integer"},"messages_this_month":{"type":"integer"},"data_points_today":{"type":"integer"},"data_points_this_month":{"type":"integer"}},"example":{"messages_today":4521,"messages_this_month":142000,"data_points_today":18000,"data_points_this_month":560000}},"IoTStats":{"type":"object","properties":{"total_devices":{"type":"integer"},"active_devices":{"type":"integer"},"connected_now":{"type":"integer"},"messages_24h":{"type":"integer"},"data_points_24h":{"type":"integer"},"active_alarms":{"type":"integer"}},"example":{"total_devices":42,"active_devices":39,"connected_now":38,"messages_24h":62000,"data_points_24h":248000,"active_alarms":3}},"WatchStateResponse":{"type":"object","description":"Watch state for an event. Contains both the internal lifecycle `status` and the viewer-facing `watch_state`. The watch page should react to `watch_state`, not `status`.","properties":{"event_id":{"type":"string","format":"uuid","example":"9a72d400-36a0-4fa5-bf7e-6883f262850a","description":"Unique event identifier"},"slug":{"type":"string","example":"product-demo","description":"URL-safe event identifier used in watch and embed URLs"},"name":{"type":"string","example":"Product Demo","description":"Event display name"},"description":{"type":"string","nullable":true,"example":"Live demo of new release features","description":"Optional event description"},"status":{"type":"string","enum":["scheduled","ready","rehearsal","live","ending","ended","processing","failed","cancelled"],"example":"live","description":"Internal lifecycle state. For UI logic, use `watch_state` instead."},"watch_state":{"type":"string","enum":["not_started","access_required","live","rehearsal_hidden","processing_replay","replay_available","ended_no_replay","cancelled"],"example":"live","description":"Canonical viewer-facing UI state. The watch page should render based on this field."},"visibility_mode":{"type":"string","enum":["public","token","password","tenant_only"],"example":"public","description":"Access control model for this event"},"requires_auth":{"type":"boolean","example":false,"description":"Whether the viewer must verify access (password, token, or login) before playback URLs are returned"},"auth_type":{"type":"string","nullable":true,"enum":["password","token","tenant_only"],"example":null,"description":"Which auth method is required, if any. Null for public events."},"scheduled_start_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-04-10T12:00:00+00:00","description":"Planned start time (ISO 8601)"},"scheduled_end_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-04-10T13:30:00+00:00","description":"Planned end time (ISO 8601)"},"timezone":{"type":"string","example":"Europe/Oslo","description":"IANA timezone for display purposes"},"countdown_enabled":{"type":"boolean","example":false,"description":"Whether to show a countdown timer before the event starts"},"playback_mode":{"type":"string","enum":["live","replay","none"],"example":"live","description":"Which playback path is currently active"},"live_playback":{"type":"object","nullable":true,"description":"Live stream playback info. Present only when the event is live and the viewer has access.","properties":{"hls_url":{"type":"string","format":"uri","example":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8","description":"HLS playback URL. Does not expire while event is live."},"protocol":{"type":"string","enum":["hls"],"example":"hls"}}},"replay_playback":{"type":"object","nullable":true,"description":"Replay playback info. Present only when the event has ended with a published replay and the viewer has access.","properties":{"url":{"type":"string","format":"uri","example":"https://s3.wayscloud.services/media-events/tenant/.../recording.mp4?X-Amz-Expires=604800","description":"Presigned replay URL. Re-resolve the event for a fresh URL after expiry."},"protocol":{"type":"string","enum":["mp4","hls"],"example":"mp4"},"duration_seconds":{"type":"integer","nullable":true,"example":5255,"description":"Total replay duration in seconds"},"expires_at":{"type":"string","format":"date-time","example":"2026-04-17T13:28:45+00:00","description":"When this presigned URL expires. Typically 7 days."}}},"embed_enabled":{"type":"boolean","example":true,"description":"Whether iframe embedding is permitted for this event"},"banner_url":{"type":"string","nullable":true,"example":null,"description":"Optional banner image URL for the watch page"},"logo_url":{"type":"string","nullable":true,"example":null,"description":"Optional logo image URL for the watch page"}}},"VerifyAccessResponse":{"type":"object","description":"Response from password or token verification. Contains an access grant and the current playback information.","properties":{"verified":{"type":"boolean","example":true,"description":"Whether verification succeeded"},"access_grant":{"type":"string","example":"ag_SqW1a2RzM1PB-bNUcXyZk0pQ7wE9vF3hJ6mT8nL","description":"Short-lived access grant. Pass this to consume-grant when playback starts."},"access_grant_expires_at":{"type":"string","format":"date-time","example":"2026-04-10T18:00:00+00:00","description":"When the access grant expires. Viewer must re-verify after expiry."},"watch_state":{"type":"string","enum":["not_started","access_required","live","rehearsal_hidden","processing_replay","replay_available","ended_no_replay","cancelled"],"example":"live","description":"Current watch state at time of verification"},"playback_mode":{"type":"string","enum":["live","replay","none"],"example":"live","description":"Which playback path is active"},"live_playback":{"type":"object","nullable":true,"description":"Live stream info, if event is live","properties":{"hls_url":{"type":"string","format":"uri","example":"https://live.wayscloud.services/hls/live/qz0bKkTt6Vjvpb7/index.m3u8"},"protocol":{"type":"string","example":"hls"}}},"replay_playback":{"type":"object","nullable":true,"description":"Replay info, if replay is available","properties":{"url":{"type":"string","format":"uri","example":"https://s3.wayscloud.services/media-events/tenant/.../recording.mp4?X-Amz-Expires=604800"},"protocol":{"type":"string","example":"mp4"},"duration_seconds":{"type":"integer","nullable":true,"example":5255},"expires_at":{"type":"string","format":"date-time","example":"2026-04-17T18:00:00+00:00","description":"When the presigned replay URL expires"}}}}},"ConsumeGrantResponse":{"type":"object","description":"Result of consuming an access grant when playback starts.","properties":{"consumed":{"type":"boolean","example":true,"description":"Whether the grant was consumed"},"already_consumed":{"type":"boolean","example":false,"description":"True if this grant was already consumed in a previous call. Token use_count is not incremented again."}}},"LiveEventCreateRequest":{"type":"object","required":["name"],"description":"Request to create a new live event.","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"example":"Product Demo","description":"Event display name"},"slug":{"type":"string","maxLength":100,"example":"product-demo","description":"URL-safe slug. Auto-generated if omitted. Unique per customer."},"description":{"type":"string","maxLength":5000,"example":"Live demo of new release features","description":"Optional event description"},"scheduled_start_at":{"type":"string","format":"date-time","example":"2026-04-10T12:00:00Z","description":"Planned start time (ISO 8601)"},"scheduled_end_at":{"type":"string","format":"date-time","example":"2026-04-10T13:30:00Z","description":"Planned end time"},"timezone":{"type":"string","default":"Europe/Oslo","example":"Europe/Oslo"},"visibility_mode":{"type":"string","enum":["public","token","password","tenant_only"],"default":"public","example":"public"},"password":{"type":"string","minLength":4,"description":"Required when visibility_mode is password"},"record_enabled":{"type":"boolean","default":true,"example":true},"replay_enabled":{"type":"boolean","default":true,"example":true},"rehearsal_enabled":{"type":"boolean","default":true,"example":true},"auto_start_on_ingest":{"type":"boolean","default":false,"example":false,"description":"Auto-transition to live when encoder connects"},"embed_enabled":{"type":"boolean","default":true,"example":true},"max_bitrate_kbps":{"type":"integer","minimum":500,"maximum":25000,"description":"Max ingest bitrate (plan-dependent)"},"retention_days":{"type":"integer","minimum":1,"maximum":365,"description":"Recording retention period"}}},"LiveEventUpdateRequest":{"type":"object","description":"Update event properties. All fields optional. Only allowed when status is draft, scheduled, or ready.","properties":{"name":{"type":"string","maxLength":255,"example":"Updated Demo Title"},"description":{"type":"string","maxLength":5000},"scheduled_start_at":{"type":"string","format":"date-time"},"scheduled_end_at":{"type":"string","format":"date-time"},"visibility_mode":{"type":"string","enum":["public","token","password","tenant_only"]},"password":{"type":"string","minLength":4,"description":"Required when changing to password visibility"},"record_enabled":{"type":"boolean"},"replay_enabled":{"type":"boolean"},"embed_enabled":{"type":"boolean"},"auto_start_on_ingest":{"type":"boolean"}}},"LiveEventTokenCreateRequest":{"type":"object","description":"Create a watch token for a token-gated event.","properties":{"label":{"type":"string","maxLength":100,"example":"VIP Attendees","description":"Human-readable label"},"max_uses":{"type":"integer","minimum":1,"maximum":100000,"example":50,"description":"Max playback sessions (consumed via access-grant model)"},"expires_at":{"type":"string","format":"date-time","example":"2026-04-10T18:00:00Z","description":"Token expiry"}}}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Service API key for server-to-server calls. Use either X-API-Key or Authorization: Bearer <key>."},"AccountAuth":{"type":"http","scheme":"bearer","bearerFormat":"wayscloud-pat","description":"Personal Access Token for account management: wayscloud_pat_xxx_yyy"},"ServiceAuth":{"type":"http","scheme":"bearer","bearerFormat":"wayscloud-service-key","description":"Service API key for provisioning and using services"},"AwsSigV4":{"type":"apiKey","in":"header","name":"Authorization","description":"AWS Signature V4 for S3-compatible storage. Use standard AWS SDKs."}}}}