# Iotamine API Reference (full) > REST + JSON API for deploying and managing VMs, standalone volumes, and IP addresses programmatically. Base URL: https://iotamine.com/api/ — see https://iotamine.com/api-docs for the same reference as a styled human-readable page, or https://iotamine.com/llms.txt for a short site index. ## Authentication Every request is authenticated with an API key sent as `Authorization: Api-Key YOUR_API_KEY`. Generate a key from the Iotamine control panel: Infrastructure → API Keys. Every key is **Read-only** by default — it can see everything the account owns, but any request other than GET/HEAD/OPTIONS gets a 403 until the key is switched to **Read & write**. There's no finer-grained scoping below that today. A key can never manage other keys — that always requires a dashboard session (see the "GET/POST /api/api_keys/" entry below). A key can optionally be locked to a single source IP (`allowed_ip`) — requests from anywhere else are rejected before authentication even runs. Not every endpoint accepts an API key. Endpoints marked "dashboard session only" below still require a logged-in dashboard session (JWT), not an API key. ## Conventions Most list endpoints return the full plain array of everything the caller owns — no pagination. A few (marked "paginated" below — VPS, Invoices, Transactions, Activity Log, Tickets) return DRF's wrapped shape instead: `{"count", "next", "previous", "results"}`, page_size=5 by default; pass `?page_size=` (up to 100000) to get everything back in one call. Most errors return `{"message": "..."}`. A few validation failures on POST/PATCH instead return DRF's own per-field shape: `{"field_name": ["error"]}`. ### Status codes - **200 / 201** — Success — 201 on resource creation, 200 otherwise. - **202** — Accepted — the action was dispatched as a background task; poll for the real result (volumes: task-status). - **400** — Bad request — missing or invalid fields, or a business rule was violated (e.g. wrong RAM tier, invalid size). - **401** — Missing or invalid API key / token. - **402** — Payment/quota required — balance below the deploy minimum, or a resource quota exceeded. - **403** — Not permitted — a read-only API key attempting a non-GET request, "a task is already running for this resource," or a suspended-for-non-payment guard. - **404** — Not found, or it exists but isn't yours — ownership is never revealed via a 403. - **409** — Conflict — e.g. already attached, wrong power state, or a live remote-provider error. - **502** — The upstream regional/hypervisor API failed or was unreachable. ## Dashboard-only endpoints (no API key) These exist in the same API but only accept a logged-in dashboard session today, not an API key: - GET /api/trusted-ips/ - GET /api/currencies/ - GET /api/pgs/ - POST /api/users/add_funds/ - GET/POST /api/api_keys/ - PATCH/DELETE /api/api_keys/{id}/ ## VPS Full lifecycle for your virtual machines — deploy, resize, power control, console access, backups, and the disks/IPs bundled onto one. ### GET /api/vps/ Auth: API key, paginated List every VPS you own — paginated, 5 per page by default. Query params: - `search` (string): Filter by hostname/IP. - `page` (int): Page number. - `page_size` (int): Results per page — up to 100000, so a large value effectively disables pagination. Example response (200): ```json { "count": 1, "next": null, "previous": null, "results": [ { "id": 412, "hostname": "web-01", "status": "active", "machine_status": "Running", "cores": 4, "ram": 8, "primary_disk": 160, "traffic": 5, "hourly_rate": 0.0248, "vps_country": "Germany", "vps_city": "Frankfurt", "os_name": "ubuntu", "ip_address": [ { "id": 88, "ip": "198.51.100.42", "is_primary": true } ], "is_building": false, "is_failed": false, "created_at": "2026-07-02T10:15:00Z" } ] } ``` ### POST /api/vps/ Auth: API key Deploy a new VM — a fresh disk + OS template, or booted from an existing volume you already own. Request body: - `hostname` (string, required): Server display name. - `password` (string, required): Root/administrator password. - `pop` (int, required): Region id (see GET /api/pop/). - `cores` (int, required): vCPU count — one of the allowed values (1–32). - `ram` (int, required): RAM in GB — one of the allowed values (2–64). - `disk` (int): Boot disk size in GB. Required unless existing_boot_volume is set. - `operating_system` (int): OS template id (see GET /api/os/). Required unless existing_boot_volume is set. - `existing_boot_volume` (int): A volume you already own — boots the VM from it instead of provisioning a fresh disk. - `existing_ip` (int): An IP address you already own — used instead of auto-assigning a new one. - `ssh_key` (int): One of your saved SSH key ids. - `disable_pwd_auth` (bool): Disable SSH password login (key-only). Example response (201): ```json { "id": 413, "hostname": "web-02", "status": "active", "cores": 4, "ram": 8, "primary_disk": 160, "vid": "b6e2...", "created_at": "2026-08-17T09:00:00Z" } ``` ### GET /api/vps/{id}/ Auth: API key Retrieve one VPS, including a live-checked machine_status. Example response (200): ```json { "id": 412, "hostname": "web-01", "machine_status": "Running", "...": "..." } ``` ### PATCH /api/vps/{id}/ Auth: API key Resize (cores/ram — must be one of the allowed tier values) or change hostname/password. Hostname and password changes require the VPS to be stopped first. Request body: - `cores` (int): New vCPU count. - `ram` (int): New RAM in GB. - `hostname` (string): New hostname — requires the VPS to be stopped. - `password` (string): New root password — requires the VPS to be stopped. Example response (200): ```json { "id": 412, "cores": 8, "ram": 16, "...": "..." } ``` ### DELETE /api/vps/{id}/ Auth: API key Destroy a VPS (dispatches an async terminate task). Owned disks/IPs survive detached by default — list ids you want released instead. Request body: - `release_ip_ids` (int[]): Owned IPs to fully release (not just detach) along with the VPS. - `release_disk_ids` (int[]): Owned volumes to fully release along with the VPS. Example response (204): ```json { "message": "VPS deleted successfully." } ``` ### GET /api/vps/{id}/start/ · stop/ · restart/ · poweroff/ Auth: API key Power actions — synchronous, plain GET requests (not POST). Blocked while a backup/restore task is already running. Example response (200): ```json { "message": "VPS started successfully." } ``` ### GET /api/vps/{id}/vnc/ Auth: API key Open a browser console session. Returns a WebSocket URL (and password, Virtualizor nodes only). Example response (200): ```json { "ws_url": "wss://iotamine.com/control/websockify/8123", "password": null } ``` ### GET /api/vps/{id}/stats/ Auth: API key Live CPU/RAM/bandwidth usage from the hypervisor, cached briefly. Example response (200): ```json { "cpu": 12.4, "used_ram": 2048, "ram": 8192, "bandwidth": 5120, "used_bandwidth": 340 } ``` ### GET /api/vps/{id}/bandwidth_history/ · metrics_history/ Auth: API key Historical bandwidth / CPU+RAM time series for graphing. Example response (200): ```json [ { "timestamp": "2026-08-17T09:00:00Z", "value": 12.4 } ] ``` ### GET /api/vps/{id}/billing/ · getpricing/ Auth: API key This VM's current billing breakdown, and its live hourly rate at the current spec. Example response (200): ```json { "hourly_rate": 0.0248, "monthly_estimate": 17.86 } ``` ### POST /api/vps/{id}/rebuild/ Auth: API key Reinstall the OS, wiping the boot disk. Requires the VPS to be stopped first. Request body: - `osid` (int, required): OS template id to install. - `new_pass` (string, required): New root/administrator password. - `conf_pass` (string, required): Must match new_pass. Example response (200): ```json { "message": "VPS rebuild has been started with Ubuntu 24.04." } ``` ### GET /api/vps/{id}/available_os/ Auth: API key OS templates installable on this VPS's region/node. Example response (200): ```json [ { "id": 3, "name": "Ubuntu 24.04", "distro": "ubuntu" } ] ``` ### GET /api/vps/{id}/build_log/ Auth: API key Tail-pollable step-by-step provisioning log (regional-backed nodes only). Query params: - `after` (string): Cursor from the previous poll — omit for the first call. Example response (200): ```json { "entries": [ { "step": "Allocating disk", "status": "done" } ], "cursor": "18" } ``` ### GET /api/vps/{id}/listbackup/ · getbackupcost/ Auth: API key List existing backups and their storage cost. Backups are supported on Virtualizor-backed nodes only — regional-backed VMs return a 400 explaining this. Example response (200): ```json [ { "id": 9, "created_at": "2026-08-01T00:00:00Z", "size_gb": 40 } ] ``` ### POST /api/vps/{id}/createbackup/ · restorebackup/ · deletebackup/ Auth: API key Create a new backup, or restore/delete an existing one (restorebackup and deletebackup take backup_id). Async — dispatches a task. Request body: - `backup_id` (int): Required for restorebackup and deletebackup. Example response (200): ```json { "message": "Restore process has been started." } ``` ### GET / POST / DELETE /api/vps/{id}/list_disk/ · add_disk/ · delete_disk/{disk_uuid}/ Auth: API key Bundled disk management. add_disk/delete_disk are blocked (400) on regional-backed VMs — use the standalone Volumes endpoints below instead for those. Example response (200): ```json { "message": "Use the Volumes page to manage disks on this VM." } ``` ### POST / DELETE /api/vps/{id}/add_ip/ · delete_ip/{ip_addr}/ Auth: API key Bundled IP management — same regional restriction as add_disk above; use the standalone IP Addresses endpoints for regional-backed VMs. Example response (200): ```json { "message": "IP added." } ``` ### GET /api/vps/{id}/attachable_ips/ Auth: API key Your own unattached IPs eligible to attach to this VPS. Example response (200): ```json [ { "id": 91, "ip": "198.51.100.7" } ] ``` ### PUT /api/vps/{id}/ip_address/{ip}/ Auth: API key Set reverse DNS (rDNS) for one of this VPS's addresses. Request body: - `rdns` (string, required): The hostname to point rDNS at. Example response (200): ```json { "message": "rDNS updated." } ``` ## SSH Keys Public keys saved to your account, reusable across any VPS you deploy. ### GET /api/sshkey/ Auth: API key List your saved SSH keys. Example response (200): ```json [ { "id": 5, "title": "laptop", "ssh_key": "ssh-ed25519 AAAA...", "created_at": "2026-05-01" } ] ``` ### POST /api/sshkey/ Auth: API key Save a new public key. Validated as a real SSH public key; duplicate titles on your account are rejected. Request body: - `title` (string, required): A name for this key, unique to your account. - `ssh_key` (string, required): The public key contents (e.g. id_ed25519.pub). Example response (201): ```json { "id": 6, "title": "deploy-key", "ssh_key": "ssh-ed25519 AAAA...", "created_at": "2026-08-17" } ``` ### DELETE /api/sshkey/{id}/ Auth: API key Remove a saved key. Example response (204): ```json {} ``` ## Firewall Rules Per-VPS firewall rules, nested under the VPS resource. ### GET /api/vps/{vps_id}/firewall_rules/ Auth: API key List the custom rules on one VPS. Example response (200): ```json [ { "id": 2, "direction": "in", "ip_type": "ipv4", "decision": "accept", "protocol": "tcp", "src_port": "any", "dest_port": "22", "source": "0.0.0.0/0" } ] ``` ### POST /api/vps/{vps_id}/firewall_rules/updateRules/ Auth: API key Replace the full rule set for this VPS in one call — rules present in the array are kept/created, anything missing from it is deleted (a diff, not an append). Request body: - `(array body)` (array, required): Each item: { direction, ip_type, decision, protocol, src_port, dest_port, source }. Example response (200): ```json { "message": "Firewall rules updated." } ``` ## IP Addresses Standalone IPv4 addresses — purchased independently of any VM, attached or detached whenever you need to. ### GET /api/ip-addresses/ Auth: API key List every IP you own. Query params: - `pop` (int): Filter to one region. - `unattached` (bool): true/false — filter to addresses with (or without) a VPS attached. Example response (200): ```json [ { "id": 91, "ip": "198.51.100.7", "vps": null, "status": "active", "purchased_at": "2026-06-01T00:00:00Z" } ] ``` ### GET /api/ip-addresses/available/ Auth: API key Availability and price for purchasing new addresses in a region — the picker source before calling purchase. Query params: - `pop` (int, required): Region id. Example response (200): ```json { "pop": 2, "pop_city": "Frankfurt", "monthly_price": 1.6, "available_count": 42, "remaining_quota": 8 } ``` ### POST /api/ip-addresses/purchase/ Auth: API key Buy one or more addresses in a region. Synchronous — addresses are reserved and returned in the same response. Max 20 per call. Request body: - `pop` (int, required): Region id. - `quantity` (int, required): 1–20. Example response (201): ```json [ { "id": 104, "ip": "198.51.100.88", "status": "active" } ] ``` ### GET /api/ip-addresses/{id}/attachable_vps/ Auth: API key Your VMs in the same region as this IP, eligible to attach it to. Example response (200): ```json [ { "id": 412, "hostname": "web-01", "is_stopped": false } ] ``` ### POST /api/ip-addresses/{id}/attach/ Auth: API key Attach this address to one of your VMs (same region only). Synchronous. Request body: - `vps` (int, required): Target VPS id. Example response (200): ```json { "id": 91, "ip": "198.51.100.7", "vps": 412 } ``` ### POST /api/ip-addresses/{id}/detach/ Auth: API key Detach this address from whatever VM it's attached to. Synchronous. Example response (200): ```json { "id": 91, "ip": "198.51.100.7", "vps": null } ``` ### DELETE /api/ip-addresses/{id}/ Auth: API key Release an address for good — must be detached first. Example response (204): ```json { "message": "IP address released." } ``` ## Volumes Standalone block storage — including boot volumes. Create one, install an OS on it, set it as a VM's boot disk, or move it to a different VM entirely. Note: Every mutating volume action below is asynchronous — it returns 202 with a task_id immediately, not the finished result. Poll task-status until status is "completed" (or "failed"). ### GET /api/volumes/ Auth: API key List every volume you own. Query params: - `pop` (int): Filter to one region. - `unattached` (bool): Filter to volumes with (or without) a VPS attached. Example response (200): ```json [ { "id": 55, "name": "vol-a83f", "size": 160, "kind": "data", "vps": null, "os_name": null } ] ``` ### GET /api/volumes/available/ Auth: API key Price and eligibility for purchasing in a region — including whether this region requires naming an existing VPS at purchase time (non-fleet regions). Query params: - `pop` (int, required): Region id. Example response (200): ```json { "pop": 2, "monthly_price_per_gb": 0.0144, "allowed_sizes_gb": [20, 40, 80, 160, 320, 520], "is_fleet_backed": true, "eligible_vps": [] } ``` ### POST /api/volumes/purchase/ Auth: API key Create a new volume — a data disk, or a boot volume with nothing installed on it yet. Request body: - `pop` (int, required): Region id. - `size_gb` (int, required): One of the allowed sizes for this region. - `kind` (string): "data" (default) or "boot". - `vps_id` (int): Required for a data disk in a non-fleet region — pins the volume to that VM's own node. Example response (202): ```json { "task_id": 881, "status": "pending" } ``` ### GET /api/volumes/task-status/{task_id}/ Auth: API key Poll the result of purchase/attach/detach/destroy/install_os/resize/set_as_boot. Example response (200): ```json { "task_id": 881, "type": "volume_create", "status": "completed", "volume": { "id": 55, "size": 160 } } ``` ### GET /api/volumes/{id}/attachable_vps/ Auth: API key Your VMs in the same region as this volume, annotated with whether each already has a boot volume. Example response (200): ```json [ { "id": 412, "hostname": "web-01", "is_stopped": true, "has_boot_volume": false } ] ``` ### GET /api/volumes/{id}/available_os/ Auth: API key OS templates installable on this volume (fleet-backed volumes only). Always a plain list, empty if nothing's installable right now. Example response (200): ```json [ { "id": 3, "name": "Ubuntu 24.04", "distro": "ubuntu" } ] ``` ### POST /api/volumes/{id}/install_os/ Auth: API key Install an OS onto this volume, overwriting whatever was on it. Must be detached first. Windows requires a password; others need an SSH key, a password, or both. Request body: - `operating_system` (int, required): OS id from available_os. - `ssh_public_key` (string): Public key to install (non-Windows). - `root_password` (string): Root/administrator password. Example response (202): ```json { "task_id": 882, "status": "pending" } ``` ### POST /api/volumes/{id}/resize/ Auth: API key Grow a volume live — works whether it's attached or not. Grow-only, next size up from the allowed tiers. Request body: - `size_gb` (int, required): Must be larger than the current size. Example response (202): ```json { "task_id": 883, "status": "pending" } ``` ### POST /api/volumes/{id}/attach/ Auth: API key Attach this volume to a VM as a plain (non-boot) data disk. Same region only. Request body: - `vps` (int, required): Target VPS id. Example response (202): ```json { "task_id": 884, "status": "pending" } ``` ### POST /api/volumes/{id}/set_as_boot/ Auth: API key Set this volume as a VM's boot disk — swaps out whatever boot volume it currently has, if any. Fleet-backed volumes only; the target VM must be stopped. Request body: - `vps` (int, required): Target VPS id. Example response (202): ```json { "task_id": 885, "status": "pending" } ``` ### POST /api/volumes/{id}/detach/ Auth: API key Detach this volume from whatever VM it's on — including a boot volume. Example response (202): ```json { "task_id": 886, "status": "pending" } ``` ### DELETE /api/volumes/{id}/ Auth: API key Release a volume for good — must be detached first. Example response (202): ```json { "task_id": 887, "status": "pending" } ``` ## Regions & Operating Systems Read-only catalogs — the region and OS ids every create/deploy request above needs, plus any maintenance affecting your own infrastructure. ### GET /api/pop/ Auth: API key List every region, with per-resource hourly pricing (vCPU/RAM/disk/traffic hourly, IP monthly). Example response (200): ```json [ { "id": 2, "city": "Frankfurt", "country": "Germany", "cpu_price": 0.004, "ram_price": 0.00035, "disk_price": 0.00002, "ip_price": 1.6 } ] ``` ### GET /api/os/ Auth: API key List every installable operating system template. Example response (200): ```json [ { "id": 3, "name": "Ubuntu 24.04", "distro": "ubuntu", "virt_type": "kvm" } ] ``` ### GET /api/maintenance-events/ Auth: API key, paginated Published maintenance events relevant to you — sitewide notices, or ones touching a region/VM you own. Recently-ended events stay visible for 24h. Example response (200): ```json { "count": 1, "next": null, "previous": null, "results": [ { "id": 12, "title": "Frankfurt network maintenance", "event_type": "network", "status": "scheduled", "start_time": "2026-08-20T02:00:00Z", "end_time": "2026-08-20T04:00:00Z", "scope": "pop", "affected_pops": [ { "id": 2, "city": "Frankfurt", "country": "Germany" } ], "affected_vps": [], "is_published": true } ] } ``` ## Account, Billing & Activity Your plan tier and quota, profile, invoices, transactions, usage cost breakdown, and account activity log. ### GET /api/account/quota/ Auth: API key Your plan tier, per-resource limits, current usage, balance, and what the next tier unlocks. Example response (200): ```json { "current_tier": "starter", "is_custom": false, "limits": { "max_cores": 32, "max_ram_gb": 64, "max_disk_gb": 1040, "max_ips": 8 }, "usage": { "cores": 4, "ram_gb": 8, "disk_gb": 160, "ips": 1 }, "next_tier": "growth", "balance": 42.18, "currency": "USD", "currency_symbol": "$" } ``` ### GET /api/users/me/ Auth: API key Your account profile and balance. Example response (200): ```json { "id": 88, "email": "you@example.com", "name": "Jane Doe", "balance": 42.18, "currency": { "short_name": "USD", "symbol": "$" }, "country": "Germany", "company_name": null, "is_verified": true, "kyc_verified": false, "is_2fa": false } ``` ### PATCH /api/users/me/ Auth: API key Update your profile. A read-only key can GET this but not PATCH it. Request body: - `name` (string): Display name. - `address` (string): Billing address. - `phone_number` (string): - `country` (string): - `city` (string): - `zip_code` (string): - `company_name` (string): - `tax_id` (string): Example response (200): ```json { "id": 88, "name": "Jane Doe", "...": "..." } ``` ### GET /api/invoices/ Auth: API key, paginated List your invoices, paginated 5 per page. Query params: - `search` (string): Search by id, status, amount, or line-item name/hostname. - `ordering` (string): e.g. -created_at, total_amount. Example response (200): ```json { "count": 1, "next": null, "previous": null, "results": [ { "id": 501, "created_at": "2026-08-01", "due_date": "2026-08-08", "status": "paid", "total_amount": "17.86", "currency": { "symbol": "$", "short_name": "USD" }, "items": [ { "id": 1, "name": "web-01 compute", "hours": 720, "price": "17.86", "resource_type": "compute" } ] } ] } ``` ### GET /api/transactions/ Auth: API key, paginated List your balance transactions (top-ups, invoice payments, refunds), paginated 5 per page. balance_before/balance_after are staff-only, not shown to a customer's own key. Example response (200): ```json { "count": 1, "next": null, "previous": null, "results": [ { "id": "b6e2...", "transaction_type": "topup", "currency": { "short_name": "USD" }, "amount": "50.00", "source": "razorpay", "status": "completed", "created_at": "2026-08-01T10:00:00Z" } ] } ``` ### GET /api/usage-billing/overview/ Auth: API key Live unbilled usage (compute/storage/network/bandwidth/backups) plus already-billed cost by month and category over a date range. Query params: - `start / end` (date): YYYY-MM-DD, both optional, both required if either is given. Bounds the billed section only — unbilled is always "as of now." Defaults to the trailing 6 months. Example response (200): ```json { "currency": { "symbol": "$", "short_name": "USD" }, "unbilled": { "summary": { "compute": 4.2, "storage": 0.6, "network": 0.1, "bandwidth": 0, "backups": 0, "total": 4.9 }, "usage_rows": [], "backup_rows": [], "bandwidth_rows": [], "error_rows": [] }, "billed": { "start": "2026-03-01", "end": "2026-08-17", "by_category": { "compute": 82.1, "storage": 6.4, "network": 3.2, "other": 0, "total": 91.7 }, "by_month": [], "invoice_count": 5 } } ``` ### GET /api/usage-billing/line-items/ Auth: API key Row-by-row usage line items backing the overview above, in either direction. Query params: - `status` (string): "unbilled" (default) or "billed". - `start / end` (date): YYYY-MM-DD — billed only, both required if either given. Defaults to the trailing 6 months. - `category` (string): "all" (default), "compute", "storage", or "network". - `search` (string): Hostname substring. - `page / page_size` (int): page_size default 10, capped at 100 — a different pagination shape than the DRF-paginated endpoints elsewhere on this page. Example response (200): ```json { "count": 1, "page": 1, "page_size": 10, "num_pages": 1, "results": [ { "id": "usage-9001", "type": "Compute", "category": "compute", "hostname": "web-01", "cost": 4.2, "date": "2026-08-17", "billed": false } ] } ``` ### GET /api/activity/ Auth: API key, paginated Your account activity log, paginated 5 per page. Query params: - `search` (string): Search action/description/ip_address/user_agent. Example response (200): ```json { "count": 1, "next": null, "previous": null, "results": [ { "id": 9001, "action": "vps.create", "description": "Created VPS web-01", "vps": 412, "ip_address": "198.51.100.10", "timestamp": "2026-08-17T09:00:00Z" } ] } ``` ### GET / POST /api/api_keys/ Auth: dashboard session only, not an API key List or create your API keys. Dashboard session only — a key can't be used to manage keys. Request body: - `name` (string, required): A label for this key. - `scope` (string): "read_only" (default) or "read_write". - `allowed_ip` (string): Optional — lock this key to one source IP. Example response (200): ```json { "id": 4, "name": "ci-deploy", "key": "b0e2b6b0-...-9f21", "scope": "read_only", "is_active": true, "allowed_ip": null, "created_at": "2026-08-17T09:00:00Z" } ``` ### PATCH / DELETE /api/api_keys/{id}/ Auth: dashboard session only, not an API key Update (e.g. deactivate, widen scope, set allowed_ip) or permanently delete a key. Dashboard session only. Example response (200): ```json { "id": 4, "is_active": false } ``` ## Support Tickets Open and reply to support tickets — the same conversation the dashboard shows, reachable from your own code too. ### GET /api/departments/ Auth: API key List support departments, to pick one when opening a ticket. Example response (200): ```json [ { "id": 1, "name": "Billing" }, { "id": 2, "name": "Technical" } ] ``` ### GET /api/tickets/ Auth: API key, paginated List your tickets, paginated 5 per page. Example response (200): ```json { "count": 1, "next": null, "previous": null, "results": [ { "id": 77, "subject": "VM unreachable", "priority": "high", "department": { "id": 2, "name": "Technical" }, "vps": null, "status": "open", "assigned_to": null, "created_at": "2 hours ago" } ] } ``` ### POST /api/tickets/ Auth: API key Open a new ticket. Request body: - `subject` (string, required): - `department_id` (int, required): From GET /api/departments/. - `message` (string): Opening message. - `priority` (string): e.g. "low" / "medium" / "high". - `vps_id` (int): Optional — associate this ticket with one of your VMs. Example response (201): ```json { "id": 78, "subject": "VM unreachable", "status": "open", "...": "..." } ``` ### GET /api/tickets/{id}/ Auth: API key Retrieve one ticket, including its full reply thread embedded (unpaginated). Example response (200): ```json { "id": 77, "subject": "VM unreachable", "replies": [ { "id": 1, "message": "...", "replied_by": "...", "created_at": "2 hours ago" } ] } ``` ### GET /api/tickets/{ticket_id}/replies/ Auth: API key, paginated The reply thread, paginated — the same data as the ticket detail's embedded replies field, in pages. Example response (200): ```json { "count": 1, "next": null, "previous": null, "results": [ { "id": 1, "message": "We're looking into it.", "replied_by": "...", "created_at": "1 hour ago", "images": [] } ] } ``` ### POST /api/tickets/{ticket_id}/replies/ Auth: API key Post a reply on a ticket you have access to. Request body: - `message` (string, required): - `uploaded_images` (file[]): Up to 5 images, 5 MB each — send as multipart/form-data, not JSON, when attaching any. Example response (201): ```json { "id": 2, "message": "Thanks, still seeing it.", "created_at": "just now" } ```