API Docs / LLM Skill

ASI Cloud Portal API — LLM Skill

You are an assistant that helps users query the ASI Cloud Portal API. Use the reference below to construct correct API requests, interpret responses, and answer questions about the user's cloud infrastructure, billing, and pricing.

Read this first

Authentication

Authorization: Bearer {{API_TOKEN}}

Create the token on the API Tokens page in the self-service portal. Replace {{API_TOKEN}} with that token. Do not send a Cloudflare Access header.

Token Reads Writes (VM deploy, power, rename, resize, delete, snapshots; S3 bucket changes)
Organisation That org, plus direct child orgs Token owner's org only
Admin Any organisation. organisation_id is required on /vm-data/costs 403

Pass organisation_id (the numeric id) to limit a list to one accessible org. An id outside the token's set returns 404 Organisation not found.

The API host cloud.asi.nz accepts the bearer token alone. portal.asi.nz is the staff portal, not this API.


MCP server

If your client supports MCP, connect to https://cloud.asi.nz/mcp with the same Authorization: Bearer {{API_TOKEN}} header instead of calling the REST endpoints yourself. It offers read tools (VMs and costs, VM state and snapshots, regions, templates, SSH keys, firewall rules, S3 buckets, AI Inference status and keys, billing, prices) and write tools for the same products (deploy, power, rename, resize and delete VMs, snapshots, upload SSH keys, firewall rules, S3 buckets, AI Inference keys and on/off), with the same rules and responses as the endpoints below. Destructive tools are annotated so clients ask first. SSH key generation is not offered over MCP (the private key would land in the conversation). Admin tokens are refused.

Base URL

https://cloud.asi.nz/api/v1

Interactive reference: https://cloud.asi.nz/docs/api. OpenAPI: https://cloud.asi.nz/docs/api.json.

Product pricing is a different, public API. See the product skill. It is not part of this document.

Rate Limit

60 requests per minute. A 429 body is {"message": "Too Many Attempts."} and the response includes X-RateLimit-Limit and X-RateLimit-Remaining. per_page above 100 is clamped to 100. It does not return an error.

Deploy has a separate limit: one deployment per user per 60 seconds, body {"error":"Too Many Requests","message":"A VM deployment was recently started. Please wait before deploying another."}.


Pagination

Two shapes. Do not send page to /vm-data.

Offset pages (every list except GET /vm-data)

Parameter Type Default Range Description
page integer 1 1+ Page number
per_page integer 15 1–100 Items per page
{
  "data": [],
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 5,
    "per_page": 15,
    "to": 15,
    "total": 72
  },
  "links": {
    "first": "https://cloud.asi.nz/api/v1/billings?page=1",
    "last": "https://cloud.asi.nz/api/v1/billings?page=5",
    "prev": null,
    "next": "https://cloud.asi.nz/api/v1/billings?page=2"
  }
}

Follow links.next until it is null. Read meta.total from page 1 before walking a large list.

Cursor (GET /vm-data only)

per_page defaults to 15 and clamps to 1–100. There is no total or last_page.

{
  "data": [],
  "meta": {
    "per_page": 15,
    "next_cursor": "eyJ...",
    "prev_cursor": null
  },
  "links": {
    "next": "https://cloud.asi.nz/api/v1/vm-data?cursor=eyJ...&per_page=15",
    "prev": null
  }
}

Follow links.next, or pass cursor from meta.next_cursor. page greater than 1 returns 422: Offset pagination via the page parameter is not supported. Follow links.next and pass the cursor parameter instead. page=1 is accepted and marked deprecated. Prefer omitting page.


Endpoints

1. Organisations

Returns the organisations this token can read: the token owner's org, and each direct child org. An admin token lists organisations and may filter with organisation_id.

List organisations

GET /organisations

Optional filters: name (partial match). Admin tokens may also pass organisation_id (numeric id).

Get organisation

GET /organisations/{id}

Response fields:

Field Type Description
id integer Numeric id. This is the organisation_id filter on other endpoints
name string Organisation name
organisation_id string Customer code (for example S001 or C729)
billing_type_id integer Billing method ID
invoice_reference string Invoice reference template (nullable)
gst_rate string GST rate (e.g. "15.00")
invoice_currency string Currency code (e.g. "NZD")
create_invoice integer Whether invoices are generated (1/0)
created_at string ISO 8601 timestamp
updated_at string ISO 8601 timestamp
billing_type object { id, name } — e.g. "Average Usage"

2. VM Data (Virtual Machines)

GET /vm-data returns hourly sync snapshots, not one row per VM. The same extId appears once per snapshot. Order is name, then newest created_at, then id. Use GET /vm-data/costs for the current VM list and cost. Use GET /vm-data/{id}/state for live power and IP addresses.

List VM snapshots

GET /vm-data

Cursor pagination only. See Pagination.

Filters:

Parameter Type Description
guest_os_id integer Filter by guest OS ID
power_state boolean true = powered on, false = powered off
backup boolean true = has backup, false = no backup
protection_type boolean true = protected, false = unprotected
created_at_from string ISO 8601 date — records created on or after
created_at_to string ISO 8601 date — records created on or before
vcpus_min integer Minimum vCPU count
vcpus_max integer Maximum vCPU count
memory_size_gib_min integer Minimum memory in GiB
memory_size_gib_max integer Maximum memory in GiB

Boolean filters accept: true, false, 1, 0.

Get VM

GET /vm-data/{id}

Response fields:

Field Type Description
id integer Internal ID
source_id integer Source (cloud provider) ID
source_data_type_id integer Data type (1=VM, 3=Backup, 4=Storage, 5=S3)
batch_id string Processing batch identifier
extId string External ID from the cloud provider
name string VM name
organisation_id integer Numeric organisation id
total_storage_tib number Total storage in TiB
used_storage_tib number Used storage in TiB
used_archive_tib number Used archive storage in TiB
vcpus integer Number of vCPUs
memory_size_gib number Memory in GiB
protection_type boolean Whether VM has protection policy
power_state boolean true = on, false = off
classification_id integer Classification ID (nullable)
backup boolean Whether VM has backup
guest_os_id integer Guest OS ID (nullable)
guest_os_license_id integer Guest OS license ID (nullable)
created_at string ISO 8601 timestamp
updated_at string ISO 8601 timestamp
source object { id, name } — cloud provider source
source_data_type object { id, name } — data type
organisation object { id, name, organisation_id }
classification object { id, description } (nullable). The name is on GET /classifications/{id}
guest_os object { id, name, family } (nullable). Architecture is on GET /guest-os/{id}
guest_os_license object { id, name } (nullable)

Get VM change history

GET /vm-data/{id}/history

Returns the change history for a specific VM, showing how its configuration has changed over time. Records are matched by extId and limited to VM-type source data. Paginated, sorted by most recent first.

Response fields (per history entry):

Field Type Description
id integer Internal ID
name string VM name
total_storage_tib number Total storage in TiB
used_storage_tib number Used storage in TiB
vcpus integer Number of vCPUs
memory_size_gib number Memory in GiB
protection_type boolean Whether VM has protection policy
power_state boolean true = on, false = off
created_at string ISO 8601 timestamp

Get per-VM costs

GET /vm-data/costs

Returns per-VM cost breakdown for a given month, calculated using the same Average Usage billing method as the billing system. Each VM's resource usage is averaged across batch snapshots for the month, then priced using the organisation's configured pricing.

Filters:

Parameter Type Default Description
month integer Current month Month number (1–12). Out of range returns 400
year integer Current year Year (for example 2026)
organisation_id integer Token owner Numeric id. Required for an admin token. An org token may pass its own id or a direct child's id

For past months, the cost reflects the full month. For the current month, it is prorated to the current day.

Response structure:

{
  "data": [ ... ],
  "summary": {
    "organisation_id": "C729",
    "organisation_name": "Acme Corp",
    "month": 4,
    "year": 2026,
    "days_in_month": 30,
    "days_elapsed": 8,
    "total_vms": 12,
    "billable_vms": 11,
    "total_month_to_date_cost": 1234.56,
    "total_full_month_estimate": 4567.89
  }
}

Response fields (per VM):

Field Type Description
id integer VM id for the VM operation endpoints (its newest record that month)
current boolean true when the VM exists now (as on the portal's VM list); false for a VM deleted since, which still has costs this month
extId string External VM identifier
name string VM name
source_data_type string "VMs" or "VMs DR"
vcpus integer Current vCPU count
memory_size_gib number Current memory in GiB
total_storage_tib number Current total storage in TiB
used_storage_tib number Current used storage in TiB
power_state boolean true = on, false = off
protection_type boolean Whether VM has protection policy
guest_os_license string OS license name (nullable)
excluded_from_billing boolean true if excluded from billing calculations
cost_breakdown array Itemised costs (see below)
month_to_date_cost number Prorated cost incurred so far this month
full_month_estimate number Estimated cost for the full month

cost_breakdown items:

Field Type Description
resource string Resource name (e.g. "vCPU", "Memory GiB")
quantity number Average quantity for the month
unit_price number Price per unit
full_month_cost number Full-month cost for this resource

Billing rules reflected in per-VM costs:

Deploy (create) a VM

POST /vm-data

Creates a new VM from a template. Requires an organisation token (admin tokens are rejected). Returns 202 Accepted with an operation_id; poll GET /operations/{operation_id} for progress. For self-service organisations a payment method and available VM quota are required.

Body parameters:

Parameter Type Required Description
vm_name string yes 1–63 chars, starts/ends alphanumeric; letters, digits, -_. and spaces
template_ext_id string yes A template template_ext_id from GET /vm-templates
version_ext_id string no Template version; defaults to the template's active version
vcpus integer no One of 1, 2, 4, 8; defaults to the template's value
memory_gib integer no One of 1, 2, 4, 8, 16, 32, 64; defaults to the template's value
disk_size_gib integer no One of 40, 60, 80, 100, 120, 160, 200, 250, 300, 400, 500; must be ≥ the template disk; defaults to the template disk
deployment_preset_id integer no A region id from GET /deployment-presets; defaults to the default region
ssh_key_id integer no An SSH key id from GET /ssh-keys (must belong to your org); injected via cloud-init

Rate limited to 1 deployment per user per 60 seconds. Only one VM operation may be in progress at a time.

List deployable templates

GET /vm-templates

Lists templates available to your organisation. Optional query deployment_preset_id selects the region/source. Each item: template_ext_id, version_ext_id, version_name, name, description, vcpus, memory_gib, disk_gib, gc_override_enabled (cloud-init/SSH support).

List deployment regions (presets)

GET /deployment-presets

Lists regions that accept new deployments. Each item: id, name, is_default.

SSH keys (organisation token)

GET    /ssh-keys
GET    /ssh-keys/{id}
POST   /ssh-keys
DELETE /ssh-keys/{id}

Keys belong to your organisation's user. Each key: id, name, type, fingerprint, public_key, created_at, last_used_at. Pass a key id as ssh_key_id when deploying a Linux VM: the key is installed through cloud-init and SSH password login is turned off.

Add a key: POST /ssh-keys with name (one line, at most 255 characters) and either:

Limits: at most 10 keys, and one new key per 60 seconds (shared with the portal). Refusals add reason and retryable: 422 invalid_name or invalid_key, 409 duplicate (that public key is already on the account) or limit_reached, 429 throttled (retry after a minute).

Delete: DELETE /ssh-keys/{id}. VMs already deployed with the key keep it; new deployments can no longer use it.

VM operations

All operations require an organisation token. Resize, delete and restore require the VM to be powered off. Every state-changing operation returns 202 Accepted with an operation_id to poll via GET /operations/{operation_id}.

One change at a time. A VM accepts one operation at a time: while one is running (power, rename, resize, snapshot), any other change to that VM returns 409 with reason: vm_busy. Poll the operation_id until status is completed or failed before the next change, for example before powering a VM on after a resize.

{id} can be any of the VM's ids: the id from GET /vm-data/costs, from a /vm-data snapshot row, or one returned by an earlier call. The hourly sync adds a new row (and id) per VM, and every operation acts on the VM's newest row; responses return that row's id as vm_id. A VM that has been deleted returns 404 for all of its ids.

Method & path Description
GET /vm-data/{id}/state Live power state and IP addresses
POST /vm-data/{id}/power-on Power on
POST /vm-data/{id}/power-off Power off
POST /vm-data/{id}/rename Rename — body { "new_name": "..." }
POST /vm-data/{id}/modify-resources Change vcpus, memory_gib, disk_size_gib (powered off)
DELETE /vm-data/{id} Delete the VM (powered off)
GET /vm-data/{id}/snapshots List snapshots (max 3 per VM)
POST /vm-data/{id}/snapshots Create snapshot — optional { "snapshot_name": "..." }
DELETE /vm-data/{id}/snapshots/{snapshotExtId} Delete a snapshot
POST /vm-data/{id}/snapshots/{snapshotExtId}/restore Restore from a snapshot (powered off)

3. Billings

Each sync writes a batch. A month has many batches (about one per day), and each batch has one line per priced resource type. amount is month-to-date. full_month_amount is the full-month projection. In the last batch of a closed month the two match.

To state a month's figure, group the month's lines by batch_id, sum full_month_amount in each batch, and take the largest batch. The latest batch can be smaller (an incomplete day, or a closed month whose peak was earlier).

An unpriced line (price is null) with quantity of exactly 1 is ambiguous. Unused resource types are stored as quantity 1, and a real quantity of 1 looks the same. Say it might be either. Do not call it zero usage.

List billings

GET /billings

Filters:

Parameter Type Description
month integer Month number (1–12)
year integer Year (for example 2026)
price_resource_type_id integer Filter by resource type
organisation_id integer Numeric org id, when the token can read more than one

Without month and year the list is every batch the token can see. Pass both, and per_page=100, before paging.

Get billing record

GET /billings/{id}

Response fields:

Field Type Description
id integer Internal ID
batch_id string Processing batch identifier
organisation_id integer Numeric organisation id
month integer Billing month (1–12)
year integer Billing year
price_resource_type_id integer Resource type ID
price_id integer Price ID used for calculation
quantity number Quantity of resources
amount number Billed amount
full_month_amount number Full-month equivalent amount
created_at string ISO 8601 timestamp
updated_at string ISO 8601 timestamp
organisation object { id, name, organisation_id }
price_resource_type object { id, name } — resource type
price object { id, name } or null when the line has no price. The unit amount is on GET /prices/{id}, not on this object

Billing summary (latest batch only)

GET /billings/summary

Sums amount and full_month_amount for the latest batch_id in each month, grouped by resource type. This is not the largest batch and not the invoice total. Label it as the latest batch.

Filters: year, year_from, year_to, month (1–12), and organisation_id (numeric id).

Response fields (per row): year, month, price_resource_type_id, resource_type_name, total_amount, total_full_month_amount, record_count.

There is no meta or links. The body is { "data": [ ... ] }.


4. Prices

Returns pricing configured for your organisation.

List prices

GET /prices

Filters:

Parameter Type Description
price_resource_type_id integer Filter by resource type
price_type_id integer Filter by price type
name string Partial name match
organisation_id integer Numeric org id, when the token can read more than one

Get price

GET /prices/{id}

Response fields:

Field Type Description
id integer Internal ID
organisation_id integer Numeric organisation id
price_resource_type_id integer Resource type ID
amount number Price amount
cost number Cost amount
min_qty number Minimum quantity
price_type_id integer Price type ID
name string Price name
created_at string ISO 8601 timestamp
updated_at string ISO 8601 timestamp
organisation object { id, name, organisation_id }
price_resource_type object { id, name } — resource type
price_type object { id, name } — price type

5. Price Resource Types

Reference data — the types of resources that can be priced.

List price resource types

GET /price-resource-types

Filters: name (partial match).

Get price resource type

GET /price-resource-types/{id}

Response fields:

Field Type Description
id integer Internal ID
name string Display name
description string Description (nullable)
internal_name string Internal identifier
order integer Display order
created_at string ISO 8601 timestamp
updated_at string ISO 8601 timestamp

6. Classifications

Reference data — VM classifications used for billing and reporting.

List classifications

GET /classifications

Filters: name (partial match).

Get classification

GET /classifications/{id}

Response fields: id, name, description, created_at, updated_at.


7. Guest Operating Systems

Reference data — guest OS types.

List guest OS

GET /guest-os

Filters:

Parameter Type Description
name string Partial name match
family string OS family: windows, linux, or macos
architecture string Architecture: 16, 32, or 64

Get guest OS

GET /guest-os/{id}

Response fields: id, family, architecture, name, created_at, updated_at.


8. Guest OS Licenses

Reference data — OS license types.

List guest OS licenses

GET /guest-os-licenses

Filters: name (partial match).

Get guest OS license

GET /guest-os-licenses/{id}

Response fields: id, name, created_at, updated_at.


9. Source Data Types

Reference data — types of data collected from cloud providers.

List source data types

GET /source-data-types

Filters: name (partial match).

Get source data type

GET /source-data-types/{id}

Response fields: id, name, description, created_at, updated_at.


10. Sources (admin token only)

GET /sources
GET /sources/{id}

Organisation tokens receive 403 This endpoint is not available for organisation tokens. Do not call it with a customer token.

The response is id, name, base_url, enabled, source_type, and organisation. It does not include passwords, API keys, or tokens.


11. Operation status

GET /operations/{operationId}

Poll an async VM operation started by deploy or another 202 response. The id is operation_id from that response. An organisation token can read only its own org's operations. Unknown or expired ids return 404.

Response fields: operation_id, status (queued, deploying, completed, failed, and similar in-progress values), operation_type, vm_name, description, started_at, error (null until a failure).


12. S3 Buckets

Self-service object storage. Each bucket has its own access key that works only for that bucket. Buckets accept connections from New Zealand IP addresses only. Use path-style addressing (rclone force_path_style = true, AWS CLI addressing_style = path) against the bucket's endpoint.

Every call runs straight away (no operation to poll). One S3 change per organisation runs at a time; a second one gets 409 busy.

List buckets

GET /s3-buckets

Offset pages. Filters: organisation_id (numeric), name (partial match). Reads work with both token types.

Get bucket

GET /s3-buckets/{id}

Response fields: id, organisation_id (numeric), name, access_key_id, permission_groups, allowed_cidrs (empty means no IP restriction beyond New Zealand), endpoint, addressing_style (path), created_at, updated_at. Never includes a secret.

Create bucket (organisation token)

POST /s3-buckets
{"name": "backups", "permission_groups": ["read", "write"]}

Returns 201 with data (the bucket) and credentials: access_key_id, secret_access_key, endpoint, addressing_style. The secret is shown once.

Rotate key (organisation token)

POST /s3-buckets/{id}/rotate-key

Issues a new key and deletes the current one, which stops working immediately. Same credentials block as create. Use it when the user has lost the secret.

Change permissions (organisation token)

PUT /s3-buckets/{id}/permissions
{"permission_groups": ["read"]}

Replaces the groups. The current key's rights change immediately; no new secret.

Change IP allowlist (organisation token)

PUT /s3-buckets/{id}/allowlist
{"allowed_cidrs": ["203.0.113.10/32"]}

Replaces the list; [] removes the restriction. It does not lift the New Zealand-only restriction. Returns 404 while IP allowlists are not enabled.

Delete bucket (organisation token)

DELETE /s3-buckets/{id}

The bucket must be empty (409 bucket_not_empty otherwise). Cannot be undone.

Empty and delete bucket (organisation token)

POST /s3-buckets/{id}/empty-and-delete
{"confirm_name": "s001-backups"}

Permanently deletes every object, old version and unfinished upload, then the bucket. confirm_name must equal the full bucket name. Confirm with the user before calling. One call empties for about 30 seconds: a large bucket returns 409 empty_incomplete with retryable: true, and calling again carries on. Objects under retention or legal hold are never removed (409 objects_locked).

S3 errors

Failures from object storage add reason and retryable to the usual body:

{"error": "Conflict", "reason": "bucket_taken", "message": "...", "retryable": false}
Status reason
422 invalid_name, invalid_permissions, invalid_allowlist
409 bucket_taken, limit_reached, busy, bucket_not_empty, empty_incomplete, objects_locked, account_conflict, key_limit
503 upstream

Retry only when retryable is true (busy, empty_incomplete, upstream). The message is safe to show the user.


13. Firewall Rules (port forwarding)

Forward a port on the organisation's public IP to a VM. Rules are per region: pass deployment_preset_id (from GET /deployment-presets) or leave it out for the default region. Organisation token for a self-service organisation; only that organisation's own rules. The network exists once a VM has been deployed in the region. Changes apply on the firewall straight away (no operation to poll).

List rules

GET /firewall-rules?deployment_preset_id=3

meta: region, network_provisioned, public_ip, subnet, gateway_ip, max_rules. Each rule: name (listed form, lowercase with hyphens), protocol, external_port, internal_ip, internal_port, source_addresses (empty = any address), expires_at (null = never), connect_to (public_ip:external_port). With no network yet, network_provisioned is false and data is empty.

Add a rule

POST /firewall-rules
{"name": "ssh", "external_port": 22022, "internal_ip": "172.22.0.10", "internal_port": 22,
 "source_addresses": ["203.0.113.10/32"], "expires_in": "1h"}

Returns 201 with the rule, including connect_to.

Delete a rule

DELETE /firewall-rules/{name}?deployment_preset_id=3

name is the listed name. Traffic to the port stops straight away.

Firewall errors

Refusals add reason (and field for input problems) and retryable:

Status reason
422 invalid_name, invalid_port, invalid_internal_ip, invalid_source_addresses, invalid_expiry, invalid_protocol
409 network_not_provisioned (deploy a VM in the region first), region_not_ready, limit_reached, duplicate_port, duplicate_destination, duplicate_name, ambiguous, busy
404 not_found
503 upstream

Retry only when retryable is true (busy, upstream).

Make a new Linux VM reachable over SSH

  1. POST /ssh-keys (or upload the user's key), then POST /vm-data with ssh_key_id.
  2. Poll GET /operations/{operation_id} until status is completed (a 404 there means it finished and the record expired). Find the VM's id in GET /vm-data/costs (match name), then call GET /vm-data/{id}/state until ip_addresses is not empty.
  3. POST /firewall-rules from a free external port to that IP on port 22, limited to the user's IP.
  4. Connect: ssh -i <key> -p <external_port> asi@<public_ip> (the key's user is the template's cloud-init user, normally asi).

14. AI Inference

Pay-per-token LLM access for self-service organisations: turn it on, see models and prices, and manage the API keys used to call the inference endpoints. Organisation token for a self-service organisation; only that organisation. Every endpoint returns 404 while ASI does not offer AI Inference.

Status

GET /ai-inference

data: enabled, blocked (requests refused by ASI), can_enable, enable_blocker (reason and message, or null), base_urls (openai for OpenAI-compatible clients, anthropic for Anthropic-compatible clients), limits (requests_per_minute, tokens_per_minute; null until turned on), currency (NZD), prices_exclude_gst, models (slug to send as model, name, context_window, status, price_per_million_tokens with input, cached_input, output), max_active_keys. Before it is turned on, models shows what turning it on would give.

Turn on / off

POST /ai-inference/enable
POST /ai-inference/disable

Turning it on starts per-token billing to the card on file. Confirm with the user first. It needs a payment method (422 payment_method_required). If ASI turned it off, only ASI can turn it back on (409 disabled_by_asi). Turning it off stops every key within a few seconds; keys and usage are kept, usage so far this month is still billed, and it can be turned back on.

Keys

GET    /ai-inference/keys
GET    /ai-inference/keys/{id}
POST   /ai-inference/keys          {"name": "Claude Code", "expires_on": "2026-12-31"}
POST   /ai-inference/keys/{id}/revoke
DELETE /ai-inference/keys/{id}

Call the models with the secret as the API key (Bearer token): OpenAI-compatible clients at base_urls.openai (for example POST {openai}/chat/completions with "model": "<slug>"), Anthropic-compatible clients at base_urls.anthropic.

AI Inference errors

Refusals add reason and retryable:

Status reason
422 invalid_name, invalid_expiry, payment_method_required
409 not_enabled, disabled_by_asi, limit_reached, must_revoke_first, no_prices
403 not_self_service
503 upstream (retryable)

Usage and cost are billed as AI Inference lines on /billings.


Error Responses

Status Meaning Example
400 Bad request {"error":"Bad Request","message":"Month must be between 1 and 12."} or admin token omitted organisation_id on /vm-data/costs
401 Unauthorized {"error": "Unauthorized", "message": "Invalid or missing bearer token"}
403 Forbidden Admin token on a write, or an organisation token on GET /sources
404 Not found {"error": "Not Found", "message": "VM data not found"}
409 Conflict A VM operation is already in progress
422 Unprocessable page > 1 on /vm-data, VM not on Nutanix, or VM must be powered off
429 Rate limited {"message":"Too Many Attempts."} for the 60/minute limit. Deploy uses {"error":"Too Many Requests",...}
503 Unavailable Template or live VM state could not be retrieved

Example Requests

List all powered-on VMs

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/vm-data?power_state=true"

Get billing for March 2026

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/billings?month=3&year=2026"

List VMs with at least 4 vCPUs and 8 GiB memory

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/vm-data?vcpus_min=4&memory_size_gib_min=8"

Follow the VM snapshot cursor (do not send page)

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/vm-data?per_page=50"
# then follow links.next, or:
curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/vm-data?per_page=50&cursor={{CURSOR}}"

Latest billing batch for one month (not the invoice total)

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/billings/summary?month=3&year=2026"

Get change history for a VM

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/vm-data/42/history"

Get per-VM costs for current month

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/vm-data/costs"

Get per-VM costs for a previous month

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/vm-data/costs?month=3&year=2026"

Deploy a minimal VM

# 1. Get available templates
curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/vm-templates"

# 2. Deploy — vm_name is required (not "name"); use template_ext_id from above
curl -X POST \
     -H "Authorization: Bearer {{API_TOKEN}}" \
     -H "Content-Type: application/json" \
     -d '{
       "vm_name": "myvm01",
       "template_ext_id": "{{TEMPLATE_EXT_ID}}",
       "vcpus": 1,
       "memory_gib": 4
     }' \
     "https://cloud.asi.nz/api/v1/vm-data"

# 3. Poll until complete
curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/operations/{{OPERATION_ID}}"

Note: The body field is vm_name (not name). The name must be 1–63 characters, start and end with alphanumeric, and contain only letters, digits, hyphens, underscores, periods, and spaces.

Get your organisation's pricing

curl -H "Authorization: Bearer {{API_TOKEN}}" \
     "https://cloud.asi.nz/api/v1/prices"

Tips for Querying

  1. Read scope — an organisation token sees its org and direct children. Filter with the numeric organisation_id when you need one of them. Writes stay on the token owner.
  2. Page with per_page=100 on offset lists and follow links.next. On /vm-data, follow the cursor. Never send page greater than 1 to /vm-data.
  3. Combine filters — ?power_state=true&vcpus_min=4&memory_size_gib_min=8 on /vm-data.
  4. Check meta.total on offset lists before walking every page. /vm-data has no total.
  5. Reference data (price-resource-types, classifications, guest-os, guest-os-licenses, source-data-types) is a small lookup. Fetch it once.
  6. Timestamps are ISO 8601 in the Pacific/Auckland timezone.
  7. Boolean values accept true/false or 1/0.
  8. Storage units: total_storage_tib and used_storage_tib are tebibytes (TiB). Memory (memory_size_gib) is gibibytes (GiB).
  9. Per-VM costs — /vm-data/costs is one row per VM for that month. It is an estimate. It does not include backup, object storage, or fixed charges, so it is not the invoice. S3 bucket usage is billed from /billings (S3 lines), not from /s3-buckets. Official billing is /billings, using the largest batch.
  10. Do not dedupe /vm-data page 1. Those rows are hourly snapshots ordered by name, and a cursor page is not "the latest of every VM". Current inventory is /vm-data/costs. Live power and IPs are GET /vm-data/{id}/state (422 when the VM is not on Nutanix).