Create an API token at Account, API. Send it as Authorization: Bearer vx_live_.... A token works only on the operations its scopes open, inside the organisation it was made for. It never works on staff routes and never manages tokens. Operations marked step-up need a recent password check; only a token with a destructive scope satisfies that, because the check happened when the token was made.
| Code | Status | Meaning |
|---|
account_blocked | 402 | New orders are on hold for this account because of a card dispute or an unpaid balance. Check your billing page or contact support. |
account_locked | 403 | This account is locked. Contact support to have it unlocked. |
account_not_locked | 409 | That account is not locked. |
addon_charge_open | 409 | A payment for the Backups addon is waiting. Pay it or let it expire first. |
addon_not_enabled | 409 | The Backups addon is not on for this server. |
address_in_use | 409 | That address is held by a customer's floating IP. It can't be changed here. |
address_not_reserved | 409 | That address isn't reserved. |
address_unavailable | 409 | That address is assigned, in quarantine or already reserved. |
amount_below_minimum | 409 | This amount is below what the card processor can charge. Add credit to your account first. |
app_already_installed | 409 | That app is already installed, or being installed, on this server. |
app_change_busy | 409 | Another app is being installed, updated or uninstalled on this server. Wait until it finishes. |
app_change_not_abandonable | 409 | That change has already finished or ended. |
app_change_not_ready | 409 | Only an app that finished installing can be uninstalled or updated. |
app_change_running | 409 | The server is still reporting this change as running. Wait a few minutes after its last progress, then try again. |
app_change_unsupported | 409 | That app can't be uninstalled or updated from here (yet). |
app_data_removal_changed | 409 | What an uninstall would delete changed since you looked. Check again and read the new list before you confirm. |
app_data_removal_confirm_required | 409 | Removing the app's data can't be undone. Confirm that you want this, or keep the data. |
app_first_boot_running | 409 | The setup this server was ordered with is still running. Wait until it finishes. |
app_host_unsupported | 409 | This server's host can't install apps yet. Try again later or contact support. |
app_install_busy | 409 | Another app is being installed on this server. Wait until it finishes. |
app_install_running | 409 | The server is still reporting this install as running. Wait a few minutes after its last progress, then try again. |
app_limit | 409 | This server holds as many apps as a server can. |
app_no_credentials | 409 | There are no credentials to show yet. The server delivers them when the install finishes. |
app_not_abandonable | 409 | That install has already finished or ended. |
app_plan_too_small | 409 | This server's plan is too small for that app. Resize it first or choose a lighter app. |
app_port_conflict | 409 | Another app on this server already uses a port this one needs. |
app_rate_limited | 429 | Too many installs were requested in the last hour. Try again later. |
app_restart_confirm_required | 409 | Installing an app restarts the server. Confirm that you want this. |
app_server_not_active | 409 | This server is not active, so an app can't be installed on it. |
app_server_not_running | 409 | The server must be running to install an app on it, and not in rescue mode or booted from an ISO. Start it and try again. |
app_too_many_failures | 409 | Installs on this server have failed several times today. Contact support before trying again. |
app_unsupported_os | 409 | This app installs on Debian and Ubuntu servers, and this server runs something else. |
app_up_to_date | 409 | That app is already at the newest version Vexelia offers. |
approval_not_open | 409 | This request has already been decided, or has expired. |
approval_pending | 409 | This request is still waiting for someone else to approve it. |
approval_required | 409 | This needs a second person's approval. Ask for it first. |
approval_self | 403 | Someone else has to approve your request. |
approval_stale | 409 | What this approval covered has changed since it was given. Ask again. |
approval_unusable | 409 | This approval can't be used for this action. Ask again. |
autopay_needs_card | 409 | Automatic renewal needs a saved card. Add a card, or turn automatic renewal off for these servers first. |
backup_addon_required | 402 | Backups are an addon for this server. Turn the Backups addon on to back it up. |
backup_cooldown | 429 | Manual backups are limited to 2 every 10 minutes per server. Try again shortly. |
backup_daily_cap | 429 | This server has had as many backups today as its plan allows. Try again tomorrow. |
backup_in_progress | 409 | A backup of this server is already running. Wait for it to finish. |
backup_limit_reached | 409 | This plan's backup limit is reached. Delete a backup (unlock it first if it is locked) and try again. |
backup_lock_limit | 409 | Too many backups are locked. Keep at least one slot free so new backups can replace the oldest one. |
backup_locked | 409 | That backup is locked. Unlock it first. |
backup_queued | 409 | A backup of this disk is already queued or running. |
backup_state | 409 | That backup can't be changed in its current state. Wait for it to finish or fail. |
backups_host_unsupported | 409 | The host running this server can't make or restore backups yet. Nothing was changed; try again later. |
backups_not_included | 409 | This plan doesn't include backups. |
bad_request | 400 | The request could not be read. |
bad_signature | 400 | Webhook signature invalid. |
block_in_use | 409 | This block still has addresses that are assigned, reserved or in quarantine, so it can't be deleted. |
cancel_scheduled | 409 | This server is set to be cancelled. Undo the cancellation before protecting it. |
cancel_unavailable | 409 | This server's paid period has already ended, so it can't be cancelled at the end of it. Pay the renewal to keep it, or destroy it now. |
config_path_unknown | 404 | That file is not one of the config files this game lets you edit here. |
conflict | 409 | That conflicts with existing data. |
console_node_unreachable | 409 | The host running this server can't be reached right now. The server may still be running. |
console_rebuilding | 409 | The server is being rebuilt. The console opens when it finishes. |
console_server_stopped | 409 | The server is stopped. Start it to use the console. |
console_server_suspended | 409 | The server is suspended, so its console is closed. |
console_session_limit | 409 | This console is already open somewhere, or you have too many consoles open. Close one and try again. |
console_unavailable | 503 | The console isn't available right now. Try again shortly. |
contact_limit | 409 | An organisation can have at most 10 contacts. |
contact_mail_limit | 429 | Too many confirmation emails have gone to that address or from this organisation today. Try again tomorrow. |
contact_recently_sent | 409 | We sent a confirmation email a moment ago. Try again in a few minutes. |
credentials_viewed | 409 | These credentials were already shown once and can't be shown again. If you didn't save them, reset the password on the server. |
cross_origin | 403 | Cross-origin request refused. |
dedicated_host_unsupported | 409 | This server's host is not ready for dedicated cores yet. Try again later or contact support. |
deletion_blocked | 409 | This can't be deleted yet. The fields say what is in the way. |
deletion_pending | 409 | A deletion is already waiting. Cancel it first, or let it run. |
deletion_started | 409 | The deletion has started and can no longer be cancelled. |
disk_busy | 409 | This disk is already being changed. Try again when that finishes. |
disk_charge_open | 409 | Another change to this server's disks is waiting to be paid for or applied. Pay it or let it expire first. |
disk_force_required | 409 | This server isn't running, so we can't check whether the disk is in use. Detach it with force if you are sure. If /etc/fstab still lists the disk without the nofail option, the server can fail to boot normally afterwards. |
disk_limit | 409 | This server already has as many extra disks as its plan allows. |
disk_retry | 409 | The disk can't be added just now. Your payment is kept and the disk is added as soon as it can be: try again in a minute. |
disks_not_offered | 409 | This server's plan has no extra disks. |
document_not_open | 409 | That is no longer waiting for payment. |
downgrades_disabled | 409 | Downgrades can't be scheduled right now. Contact support. |
email_change_failed | 409 | That address can't be used. Try another, or contact support. |
email_taken | 409 | That email can't be used. |
email_unverified | 403 | Confirm your email address first. We can resend the link. |
eula_required | 409 | Accept the game's current EULA before starting this server. |
eula_unavailable | 409 | No EULA is published for this game yet, so it can't be started. |
eula_version_changed | 409 | The EULA changed. Read the current text and accept it. |
event_not_dead | 409 | Only a payment event that ran out of attempts can be marked handled. |
event_processed | 409 | This payment event has already been processed. |
export_not_ready | 409 | That export is not ready, or its download time has ended. Ask for a new one. |
export_open | 409 | An export is already being prepared. Wait for it to finish, then ask again. |
ext_hook_disabled | 409 | This hook is switched off. Switch it on first. |
ext_hook_limit | 409 | The platform already has the most hooks it holds. Delete one first. |
ext_hook_replay_changed | 409 | The deliveries this replay would send are not the ones that were previewed or approved. Preview it again. |
ext_hook_replay_limit | 409 | A replay can carry at most 200 deliveries and must fit in the hook's queue of 1000. Narrow the filter, or wait for the queue to drain. |
ext_hook_replay_needs_approval | 409 | Replaying this many deliveries needs a second person's approval. |
ext_override_languages | 409 | The most languages are already in use. Remove one first. |
ext_override_no_draft | 409 | There is no draft to publish. Save one first. |
ext_override_render | 409 | This text could not be shown with the sample data. |
ext_override_stale | 409 | Someone else saved this draft after you opened it. Reload to see their version. |
ext_pane_limit | 409 | The platform already holds the most manifests it takes. |
ext_pane_no_keys | 409 | No manifest signing key is enrolled on this platform, so no manifest can be uploaded. |
ext_pane_state | 409 | This manifest is not in a state that allows that. |
ext_pane_version | 409 | A manifest with this id has this version or a higher one already. Upload a higher version. |
ext_plugin_disabled | 409 | This plugin is switched off. |
ext_plugin_limit | 409 | The platform already holds the most plugins or waiting uploads it allows. Uninstall or discard one first. |
ext_plugin_no_keys | 409 | No plugin signing key is enrolled on this platform (VEXELIA_EXT_PLUGIN_PUBKEYS), so no plugin can be installed or run. |
ext_plugin_signature | 422 | The package is not signed by a key enrolled on this platform, or it was changed after signing. |
ext_plugin_version_not_newer | 409 | A plugin with that id is installed, or was installed, at this version or a newer one. Upload a higher version. |
ext_plugins_disabled | 409 | Plugins are switched off on this platform (feature flag ext_plugins_enabled). Turning one off, discarding an upload and uninstalling still work. |
ext_template_locked | 409 | This email can't be overridden. Its wording is fixed. |
ext_upload_changed | 409 | The upload is not the one this request names. Check the plugin id, version and manifest hash against the upload. |
ext_upload_expired | 409 | This upload waited too long for approval and lapsed. Upload the package again. |
file_conflict | 409 | The file changed since you opened it. Reload it before saving. |
file_denied | 403 | The game manages that file itself, so it can't be changed here. |
file_disk_full | 409 | There isn't enough disk space left on this server for that. Free some space or upgrade the plan. |
file_exists | 409 | Something with that name already exists. |
file_not_found | 404 | That file or folder doesn't exist. |
file_path_invalid | 422 | That path isn't allowed. Use plain folder and file names without .. or special characters. |
file_special | 409 | Links and device files can't be opened here. You can rename or delete them. |
file_too_large | 413 | That is too large for the file manager (2 MB to move, 1 MB to edit). Use SFTP for big files. |
file_wrong_type | 409 | That is a folder where a file was expected, or a file where a folder was expected. |
files_busy | 429 | Too many file requests at once. Wait a moment and try again. |
files_failed | 502 | The host could not complete that file request. Try again; if it keeps failing, contact support. |
files_node_unreachable | 409 | The host running this server can't be reached right now. Your files are safe. Try again shortly. |
files_node_unsupported | 409 | The host running this server is not ready for the file manager yet. Use SFTP later, or contact support. |
files_server_suspended | 409 | The server is suspended, so its files are closed. |
files_too_many | 413 | That folder holds too many items to handle at once. |
files_unavailable | 503 | File access isn't available right now. Try again shortly. |
firewall_changed | 409 | This server's firewall changed while that ran. Try again. |
firewall_group_in_use | 409 | This rule group is used by firewall policies. Remove it from them first. |
firewall_in_use | 409 | This firewall policy is attached to servers. Detach it from them first. |
firewall_limit | 409 | You have reached the limit of firewall policies or rules. |
firewall_lockout | 409 | This change would block your own connection to the server. Apply it anyway to confirm. |
firewall_no_test | 409 | This server's firewall is not in test mode. |
firewall_test_over | 409 | The test ended and the previous firewall was put back. Apply the change again to test it. |
firewall_too_many_rules | 409 | That would give a firewall policy more rules than it can hold. Rules from its groups count too. |
firewall_version_conflict | 409 | Someone else changed this firewall. Reload it and make the change again. |
floating_ip_assigned | 409 | That floating IP is assigned to a server. Unassign it first. |
floating_ip_billing_ended | 409 | The server that paid for this floating IP has ended, so it can't be assigned. Release it, or contact support to move its billing. |
floating_ip_busy | 409 | That floating IP is changing (assigning, unassigning or being released). Try again when it finishes. |
floating_ip_charge_open | 409 | Another payment for a volume or floating IP is waiting or being applied. Pay it, or wait for it to finish or expire, then try again. |
floating_ip_confirm_required | 409 | Taking a floating IP off a server stops traffic to that address at once. Confirm that you want this. |
floating_ip_host_unsupported | 409 | This server's host is not ready for floating IPs yet. Try again later or contact support. |
floating_ip_limit | 409 | This organisation has reached its limit of floating IPs. Release one first, or ask support to raise the limit. |
floating_ip_name_taken | 409 | A floating IP of this organisation already has that name. Choose another. |
floating_ip_no_pool | 409 | There are no floating IP addresses available in this server's location right now. Try again later or contact support. |
floating_ip_not_assigned | 409 | That floating IP is not assigned to a server. |
floating_ip_other_group | 409 | That floating IP belongs to a different location from this server. A floating IP can be assigned only to servers in its own location. |
floating_ip_other_node | 409 | That floating IP stays on the host it was first assigned on. Choose a server on that host. |
floating_ip_server_full | 409 | This server already holds as many floating IPs as a server can. |
folder_not_empty | 409 | That folder isn't empty. Delete it with its contents to remove it. |
forbidden | 403 | You don't have permission to do that. |
game_address_taken | 409 | Another node already has that public address. |
game_image_exists | 409 | That image reference is already registered. |
game_image_key_missing | 409 | No game image signing key is configured, so no image can be registered. |
game_image_registry | 422 | That image is not from an allowed registry. |
game_image_signature | 422 | The signature does not verify for this image reference with the game image key. |
game_image_state | 409 | The image is not in a state that allows that. |
game_network_host_unsupported | 409 | The host for this game server can't give it a network address yet. Nothing was changed; try again later. |
game_network_in_use | 409 | Servers on this node hold its address or ports. Move them off first, or widen the range instead. |
game_unavailable | 409 | That game can't be ordered right now. Nothing was charged. |
grant_escalation | 403 | You can only give rights you hold yourself. |
idempotency_in_progress | 409 | A request with this Idempotency-Key is still running. Try again shortly. |
idempotency_key_reused | 422 | This Idempotency-Key was already used for a different request. Use a new key for a new request. |
idempotency_outcome_unknown | 409 | An earlier request with this Idempotency-Key failed on our side and may have run. Check the resource, or wait 15 minutes and send the request again. |
idempotency_replay_unavailable | 409 | This Idempotency-Key was used for a request that succeeded, but its answer was too large to keep. Read the resource to see the result. |
image_revoked | 409 | The game image this server runs from was withdrawn, so it can't be started. Contact support. |
impersonation_forbidden | 403 | This is not available while viewing an account. |
impersonation_in_progress | 409 | You are already starting a viewing session. Try again. |
impersonation_read_only | 403 | You are viewing this account read-only. Nothing can be changed. |
internal | 500 | Something went wrong on our side. |
invalid_code | 401 | That code isn't valid. |
invalid_credentials | 401 | Email or password is incorrect. |
invite_invalid | 404 | This invitation is not for your account, or it has expired or was withdrawn. |
invite_inviter_changed | 409 | The person who invited you can no longer give you that role. Ask them for a new invitation. |
invoice_not_creditable | 409 | This invoice cannot be credited this way. |
ip_reassigned | 409 | One of this server's addresses has been given to someone else, so it can't be restored. |
iso_fetch_queue_full | 429 | Three ISO downloads are already waiting. Try again when one has finished. |
iso_hash_mismatch | 422 | The ISO does not match the SHA-256 you gave. |
iso_in_use | 409 | This ISO is mounted on a server. Unmount it first. |
iso_not_iso | 422 | That file is not an ISO 9660 or UDF disc image. |
iso_quota_exceeded | 409 | This would take your ISO library over its quota. Delete an ISO you no longer need first. |
iso_size_mismatch | 422 | The upload was not the size it said it would be. Try again. |
iso_too_large | 413 | That ISO is larger than the 10 GiB allowed. |
iso_unavailable | 503 | The ISO library is not available right now. Try again later. |
iso_upload_format | 415 | Send the ISO as the raw request body with Content-Type: application/octet-stream and a Content-Length. |
job_not_idempotent | 409 | This job is not safe to run on demand. It runs on its own schedule. |
last_owner | 409 | An organisation needs at least one owner. |
last_staff_owner | 409 | This is the last owner. Make someone else an owner first. |
limit_reached | 409 | Your organisation has reached a limit. You can ask for a higher one under Account, Limits. |
limit_request_closed | 409 | This request has already been answered. |
limit_request_open | 409 | You already have an open request for this limit. We will answer it soon. |
lock_not_allowed | 409 | That account can't be locked from here: it is your own, a staff account, or not an active account. |
mail_not_retryable | 409 | Only an email that failed, ran out of retries or has an unknown result can be sent again. |
mail_secret_bearing | 409 | This email carries a single-use link, so it is never sent again. Ask the customer to request it again (verify, reset or invite). |
member_outranks | 403 | That member can do more than you can, so you can't change them. |
mod_checksum_mismatch | 502 | The download did not match the checksum this game's list pins, so nothing was installed. |
mod_fetch_failed | 502 | We could not download that file just now. Try again in a few minutes. |
mod_file_present | 409 | A different file with that name is already in the mods folder. Remove or rename it first. |
mod_not_installed | 409 | That mod is not installed from this list. |
mod_unknown | 404 | That mod is not on this game's list. |
no_capacity | 409 | That region is full right now. Try another region or a smaller plan. |
no_firewall_reset | 409 | This server has no staff firewall reset to restore. |
node_full | 409 | The node holding this server's disk has no room for it right now. |
node_unavailable | 409 | The node holding this server's disk isn't active, so it can't be restored now. |
not_cancelling | 409 | This server isn't set to cancel, or its paid period has already ended. |
not_found | 404 | Not found. |
not_in_rescue | 409 | This server isn't in rescue mode. |
not_quarantined | 409 | That address isn't in quarantine. |
not_text_file | 415 | That isn't a plain text file, so it can't be edited here. Download it instead. |
offsite_unavailable | 409 | Off-site copies are not available yet: no off-site provider is connected. |
op_not_abortable | 409 | Only an operation that hasn't been sent to the node yet can be aborted. |
op_not_resolvable | 409 | Only the latest operation that needs review can be resolved by hand. |
op_not_retryable | 409 | This operation can't be retried from its current state. |
org_erasing | 409 | This organisation is being deleted, so nothing can be changed in it. |
paid_period_ended | 409 | This server's paid period has ended or is not the current one. Renew it first, then change its disks. |
password_breached | 422 | That password has appeared in a known data breach. Choose a different one. |
payment_disputed | 409 | That payment is under a card dispute, so nothing can be refunded or credited from it until the dispute is decided. |
payment_unavailable | 409 | This payment can't be completed online. Contact support. |
paypal_declined | 409 | PayPal declined this payment. Nothing was charged. Try another way to pay. |
paypal_not_approved | 409 | PayPal hasn't confirmed your approval of this payment yet. Go back to PayPal to approve it, then try again. |
paypal_unavailable | 503 | PayPal isn't responding right now. Nothing was charged. Try again in a minute. |
period_closed | 409 | That period is closed. A correction is a reversal entry dated now, not an edit. |
period_not_ready | 409 | That period can't be closed yet. |
personal_org | 409 | Personal organisations can't be changed this way. |
plan_change_open | 409 | A plan change for this server is waiting to be paid for or applied. Pay it or let it expire first. |
plan_disk_shrink | 409 | A disk can't shrink. To move to a plan with a smaller disk, rebuild the server onto it. |
plan_in_use | 409 | Servers or open orders still use this plan, so its specifications can't change. Create a new plan and retire this one. |
plan_no_room | 409 | The host this server runs on doesn't have room for that plan right now. Try a smaller plan, or try again later. |
plan_other_arch | 409 | That plan runs on a different processor type (x86 or Arm) than this server. A server never changes it: order a new server on that plan and move your data. |
plan_other_class | 409 | That plan has a different kind of CPU (shared or dedicated) than this server. A server never changes it: order a new server on that plan and move your data. |
plan_too_late | 409 | Too little of the paid period is left to charge for an upgrade. Renew first, then change plan. |
private_network_address_taken | 409 | Another server on that private network has that address. |
private_network_already_member | 409 | This server is on that private network already. |
private_network_busy | 409 | That server's interface on the private network is changing. Try again when it finishes. |
private_network_confirm_required | 409 | Taking a server off a private network stops its traffic on that network at once. Confirm that you want this. |
private_network_firewall_ack_required | 409 | This server's firewall policy filters its public interface only, not its private network interface. Confirm that you understand this to attach it. |
private_network_full | 409 | That private network holds as many servers as a network can. |
private_network_host_unsupported | 409 | This server's host is not ready for private networks yet. Try again later or contact support. |
private_network_in_use | 409 | Servers are still attached to that private network. Take them off it first. |
private_network_limit | 409 | This organisation has reached its limit of private networks. Delete one first, or ask support to raise the limit. |
private_network_name_taken | 409 | A private network of this organisation already has that name. Choose another. |
private_network_no_room | 409 | That private network has no free address left. |
private_network_not_member | 409 | This server is not on that private network. |
private_network_other_group | 409 | That private network is in a different location from this server. A server can be attached only to networks in its own location. |
private_network_other_node | 409 | That private network is on a different host from this server. Servers on one network must run on the same host for now. |
private_network_overlap | 409 | That range overlaps another private network of this organisation. Choose a range that does not. |
private_network_server_full | 409 | This server is on as many private networks as a server can be. |
proforma_not_payable | 409 | This renewal has already been paid or is no longer due. |
purchase_blocked | 403 | Purchases are blocked for this organisation right now. Contact support. |
quarantine_abuse | 409 | This address is in quarantine because of abuse, so it can't be released early. |
quote_expired | 409 | The price you were shown has expired. Ask for a new one. |
rate_limited | 429 | Too many requests. Try again shortly. |
rdns_lookup_failed | 503 | We could not look that name up just now. Try again in a minute. |
rdns_not_confirmed | 422 | That name doesn't point to this address yet, so reverse DNS can't be set to it. |
rdns_settled | 409 | This request has already been answered. |
rebuild_unavailable | 409 | This server can't be rebuilt in its current state. Only an active server can be rebuilt. |
reference_already_recorded | 409 | That bank reference was already recorded for this method. |
refund_not_failed | 409 | This refund hasn't failed, or has already been dealt with another way. |
reinstall_needs_stopped | 409 | Stop the server before reinstalling it. |
reinstall_unavailable | 409 | This server's host can't reinstall it yet. Nothing was changed; try again later. |
renewal_charging | 409 | This server's renewal is being charged. Try again in a few minutes. |
renewal_unpaid | 409 | This server's renewal is unpaid. Place a hold first, or ask the customer to pay. |
request_declined | 403 | We can't complete this request. If you think this is a mistake, contact support. |
request_declined_limit | 409 | This request goes over a limit on your account. Contact support if you need it raised. |
request_declined_region | 403 | This isn't available for your region right now. |
rescue_unavailable | 409 | Rescue mode isn't available for this server yet. |
restore_expired | 409 | The 72-hour trash period for this server has ended. |
restore_in_progress | 409 | A restore of this server is already running. |
restore_needs_stopped | 409 | Stop the server before restoring a backup. |
restore_no_addresses | 409 | No addresses were recorded for this server when it was destroyed, so it can't be restored. |
restore_not_allowed | 409 | This server has nothing in trash to restore: its setup didn't finish, it was refunded, or its disk was already gone. |
restore_open | 409 | A restore onto this server is already waiting or running. |
restore_overdue | 402 | This server is past its paid period. Pay the open invoice before restoring a backup. |
role_builtin | 409 | Built-in roles can't be changed. |
role_escalation | 403 | You can only give a role whose permissions you hold yourself. |
role_exists | 409 | A role with that name already exists. |
role_frozen | 409 | legacy_staff is frozen. It can be taken away but never given. |
role_in_use | 409 | People still hold this role. Take it away from them first. |
role_limit | 409 | An organisation can have at most 20 roles of its own. |
role_owner_cli | 409 | The owner role is only given with the bootstrap-owner command, never from the panel. |
schedule_limit_reached | 409 | This plan's schedule limit is reached. Delete a schedule and try again. |
schedule_name_taken | 409 | A schedule with that name already exists on this server. |
schedule_task_forbidden | 403 | You don't have permission to schedule that kind of task. |
schedules_not_included | 409 | This plan doesn't include schedules. |
second_factor_needed | 401 | Enter your two-factor code to continue. |
server_busy | 409 | This server is busy with another action. Try again when it finishes. |
server_not_active | 409 | This server can't do that in its current state. |
server_not_stopped | 409 | Stop the server first. A firmware change applies when it starts again. |
server_protected | 409 | This server is protected from being destroyed. Turn the protection off first. |
service_held | 409 | A hold protects this server. Lift it first. |
sftp_unavailable | 503 | SFTP isn't available right now. |
snapshot_busy | 409 | That snapshot is being taken, restored or deleted. Try again when it finishes. |
snapshot_charge_open | 409 | A payment for a snapshot is waiting. Pay it or let it expire first. |
snapshot_limit | 409 | This organisation has reached its limit of snapshots. Delete one first, or ask support to raise the limit. |
snapshot_name_taken | 409 | A snapshot of this server already has that name. Choose another. |
snapshot_server_changed | 409 | This server's disk has changed since the snapshot was taken (it was rebuilt, restored from a backup or resized), so the snapshot can't be restored onto it. |
snapshot_server_limit | 409 | This server has reached its limit of snapshots. Delete one first to take another. |
sso_already_linked | 409 | Your account is already linked to that provider. Unlink it first to link a different identity. |
sso_last_sign_in_method | 409 | That is the only way you can sign in. Set a password or add a passkey first. |
sso_not_for_staff | 403 | Staff accounts sign in with a password and a second factor, not with an identity provider. |
sso_unavailable | 503 | Sign-in with that provider is not available right now. Use your password, or try again later. |
step_up_required | 403 | Confirm it's you to continue. |
subuser_exists | 409 | That person already has access to this server, or an invitation is waiting for them. |
subuser_limit_reached | 409 | This server has as many invitations and guests as it can have. Revoke one and try again. |
support_grant_active | 409 | Support already has access to this server. Withdraw it first to give different access. |
tax_unavailable | 503 | We can't work out the tax for this right now. Please try again later, or contact support if it keeps happening. |
template_eula_missing | 409 | No EULA text is published for this game. Publication is blocked. |
template_id_mismatch | 422 | The template's id is not the one in the address. |
template_licence_mismatch | 409 | The template's licence block does not match the owner's recorded licence check. |
template_licence_missing | 409 | No licence check has been recorded for this template by the owner, or the template does not carry it. Publication is blocked. |
template_signature | 422 | The signature does not verify for this template and version with a known key. |
template_version_exists | 409 | That version of the template is already published. Versions are immutable; publish a new version. |
template_version_older | 409 | A newer version of this template is already published. |
ticket_closed | 409 | This ticket was closed too long ago to reopen. Open a new ticket and mention this one. |
ticket_limit | 409 | This organisation has too many open tickets, or this ticket is too long. Close one first, or open a new ticket. |
token_invalid | 400 | This link is invalid or has expired. |
token_limit | 409 | This organisation has reached its limit of active API tokens. Revoke one first. |
topup_not_payable | 409 | This top-up expired or was cancelled, so it can't be paid. Start a new top-up. |
transfer_invalid | 409 | This ownership transfer can no longer be accepted. |
transfer_open | 409 | An ownership transfer is already waiting for an answer. Cancel it to start another. |
trial_needs_card | 409 | Add a card to your account first. A trial needs one on file; nothing is charged for the trial. |
trial_not_eligible | 409 | This account isn't eligible for a free trial. |
trial_open | 409 | This account already has a free trial. |
trial_server | 409 | This server is a free trial, so it has no paid changes until you keep it. Keep it first, or end the trial. |
trial_state | 409 | The trial can't do that in its current state. |
trial_unavailable | 409 | Free trials aren't available right now. |
two_factor_enrolment_required | 403 | Turn on two-factor sign-in to continue. |
two_factor_not_enrolled | 409 | Turn on two-factor sign-in on your own account first, so you are not locked out. |
two_factor_required_by_policy | 409 | Your organisation or your staff role requires a second factor, so the last one can't be removed. |
two_factor_signin_required | 403 | Sign in with your existing second factor before adding another. |
unauthenticated | 401 | Sign in to continue. |
unsupported_media | 415 | Send JSON with Content-Type: application/json. |
validation_failed | 422 | Some fields need attention. |
vat_check_rate_limited | 429 | You have asked for the VAT number to be checked several times in the last hour. We keep trying automatically; look again later. |
vat_check_stale | 409 | That VAT check is no longer the current one: the tax ID, the check or its approval state changed. Reload and review it again. |
volume_attached | 409 | That volume is attached to a server. Detach it first. |
volume_busy | 409 | That volume is changing (attaching, detaching, resizing or being deleted). Try again when it finishes. |
volume_charge_open | 409 | Another payment for a volume or floating IP is waiting or being applied. Pay it, or wait for it to finish or expire, then try again. |
volume_host_unsupported | 409 | This server's host is not ready for volumes yet. Try again later or contact support. |
volume_limit | 409 | This organisation has reached its limit of volumes. Delete one first, or ask support to raise the limit. |
volume_name_taken | 409 | A volume of this organisation already has that name. Choose another. |
volume_no_slot | 409 | This server has no free extra-disk slot: its four slots are used by disks and volumes. |
volume_not_attached | 409 | That volume is not attached to this server. |
volume_other_node | 409 | That volume's data is on a different host from this server. A volume can be attached only to servers on the host that holds it. |
weak_password | 422 | Use at least 10 characters. |
webhook_disabled | 409 | This webhook endpoint is switched off. Switch it on first. |
webhook_limit | 409 | This organisation has reached its limit of webhook endpoints. Delete one first, or ask support to raise the limit. |
games
GET /api/v1/orgs/{org}/games
List game servers
The organisation's game servers, newest first, with the state the node last reported. Creating one is part of the purchase flow.
| Response | Meaning |
|---|
| 200 | Game servers |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}
Show a game server
The server with its resources, ports and startup variables. Secret variables show only whether one is set. A server of another organisation, or a VPS, is not found.
| Response | Meaning |
|---|
| 200 | Game server |
| default | Error |
PATCH /api/v1/orgs/{org}/games/{game}
Rename a game server or change its variables
Fields left out keep their value. Variables are checked against the template's types and limits; a variable that is read-only, unknown, or no longer declared by the template is refused, and one refusal rejects the whole change. A change is recorded in Activity by key; for a secret only that it changed.
Request body (JSON):
{
"name": "string",
"variables": {}
}| Response | Meaning |
|---|
| 200 | Updated server |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/access
List who has been given access to a game server
The server's invitations and guests (live ones first, then the last ones that ended), the five rights a grant can carry, which of them the caller may give (`grantable`: a right is given only by someone who holds it), and the limits. A guest is another account with a scoped part of one server and no membership of the organisation. A server of another organisation is not found. Needs games.subusers (owners and admins by default); no API token scope opens it. State: integrated on fakes.
| Response | Meaning |
|---|
| 200 | The access list |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/access
Invite another account to a game server
Invites the person at `email` with one or more rights: console, power, files, backups, schedules. They are sent an email (no link that works by itself: they sign in with that address, or create an account with it, and accept on the Games page), and the invitation can be accepted for 14 days. Seeing the server, its state, address and graphs is implied. A right is given only by someone who holds it (grant_escalation otherwise), and the check is made again when the invitation is accepted (invite_inviter_changed if the person who set it can no longer give it). Needs step-up. Refused with subuser_exists for a person with a live invitation or access, and subuser_limit_reached at the server's limit (an invitation past its time is closed first and does not count). The email goes to an address that may not have asked for it, so the MAIL is limited: one an hour to the same address for the same server, three a day to one address across all organisations, ten a day from one person and twenty a day from one organisation (counted in keyed fingerprints, never the address; plus-tags and gmail dots do not make a new address). A limit never blocks the invitation: it is still made and listed (and the person sees it under their own invitations), and the answer says `mail_sent: false`. The names in the mail are quoted and marked as the sender's. Audited as game.subuser_invited, with no address in the event. Needs games.subusers. State: integrated on fakes.
Request body (JSON):
{
"email": "string",
"permissions": [
null
]
}| Response | Meaning |
|---|
| 201 | Invited |
| default | Error |
DELETE /api/v1/orgs/{org}/games/{game}/access/{grant}
Revoke an invitation or a grant on a game server
Ends a pending invitation or an active grant. It takes effect on the guest's next request, and any console they have open on the server ends at the gateway's next heartbeat. Needs no step-up: taking access away is never held up. A short-lived SFTP credential the guest already holds stays valid until it expires (10 minutes; the SFTP service itself is not built). Audited as game.subuser_revoked. Needs games.subusers. State: integrated on fakes.
| Response | Meaning |
|---|
| 204 | Revoked |
| default | Error |
PUT /api/v1/orgs/{org}/games/{game}/access/{grant}
Change what an invited account may do on a game server
Replaces the rights of a pending or active grant. The new set must be one the caller holds in full (grant_escalation). It is read on every request, so a right taken away is gone on the guest's next one, and a console that was open when the console right is taken away ends at the gateway's next heartbeat (within seconds). Needs step-up. Audited as game.subuser_changed. Needs games.subusers. State: integrated on fakes.
Request body (JSON):
{
"permissions": [
null
]
}| Response | Meaning |
|---|
| 200 | Changed |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/backups
List a game server's backups
The server's backups, newest first (at most 200; a deleted one is not listed), the restores asked for on it (the latest 20), what the plan allows (`limits.backups` backups at once, `limits.used` held now, `limits.max_locked` of them lockable, one slot always stays free so a scheduled backup can replace the oldest), and why a new backup or a restore would be refused right now (`can_create`, `can_restore`) so the panel can say so first. A backup is a copy of the server's data directory only: never its environment, its startup command, its secret variables or the host's files. It IS the whole data directory, so a config file the game keeps a secret in is in it, encrypted under your organisation's key; a game's template can name such files to leave out. State pending means waiting for the host; running, the host is archiving it; ready, its manifest was read back and checked; failed, with `error_code` (store_unavailable, backup_too_large, backup_too_many_files, node_no_backups, window_missed, server_busy, superseded and others); deleting, its stored objects are being removed. A ready backup may carry `error_code` resume_failed: the game did not confirm that writes were switched back on after the backup, so restart the server. `quiesced` says whether the game confirmed it had stopped writing first; false means the copy is crash-consistent (like a power cut), which games recover from. A server of another organisation is not found. Needs games.backups; no API token scope opens it. State: integrated on fakes; no real container node, object store or key service has run this.
| Response | Meaning |
|---|
| 200 | The backups |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/backups
Back a game server up now
Records a backup as pending and answers 202; the worker queues the host's job as soon as the server's lane is free. Only one backup of a server waits or runs at a time (backup_in_progress), and while it runs a graceful stop or restart answers server_busy (a kill still works and ends the backup as superseded). Works while the server runs (the template's save commands are sent first and resume afterwards) and while it is stopped. A manual backup at the plan's count is refused with backup_limit_reached: delete one first (a scheduled backup at the count goes ahead if an unlocked scheduled one can be replaced, and only once it is verified ready does the oldest go: a failed backup never costs a restore point); backups of a plan that has none are refused with backups_not_included. Refused with backup_daily_cap (429) after twice the plan's count of backups in 24 hours, whatever became of them, and with server_not_active unless the server is active, and backups_host_unsupported when the host's software has not reported that it makes backups (nothing is queued). The size is capped by the plan's disk and the platform's own ceiling; a larger one fails as backup_too_large. A backup is encrypted with a key that belongs to your organisation before it leaves the host. A server of another organisation is not found. Needs games.backups.
Request body (JSON):
{
"name": "string"
}| Response | Meaning |
|---|
| 202 | Recorded |
| default | Error |
DELETE /api/v1/orgs/{org}/games/{game}/backups/{backup}
Delete a game server backup
Marks a ready or failed backup for deletion and answers 202; the worker then removes every stored object with the credential only it holds, and the backup disappears from the list. A locked backup is refused with backup_locked; one that is still waiting or running with backup_state (as is one a restore is waiting on or running from). A backup of another server or organisation is not found. Needs games.backups.
| Response | Meaning |
|---|
| 202 | Deleting |
| default | Error |
PATCH /api/v1/orgs/{org}/games/{game}/backups/{backup}
Lock or unlock a game server backup
A locked backup is never removed by retention and cannot be deleted until it is unlocked. At most one fewer than the plan's count may be locked (backup_lock_limit), so a scheduled backup can always replace the oldest unlocked one. A failed or deleting backup cannot be locked (backup_state). A backup of another server or organisation is not found. Needs games.backups.
Request body (JSON):
{
"locked": false
}| Response | Meaning |
|---|
| 200 | The backup |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/backups/{backup}/restore
Restore a backup over a stopped game server
Replaces the server's files with the backup's and answers 202; the worker queues the host's job. The server must be stopped (offline or crashed) when this is asked and again when the job is queued, or the restore fails as server_not_stopped with nothing changed (restore_needs_stopped answers now). Needs step-up and the server's name in `confirm_name` (case and spaces do not matter; validation_failed otherwise). The host first reads and checks every part of the backup and unpacks it beside the live files; a damaged backup is refused (corrupt) with the server's files untouched. The files it replaces are kept beside them on the host as the pre-restore copy until the next restore. The server stays in state restoring until the host reports; a restore left in review is shown to support. Only one restore of a server is open at a time (restore_in_progress). Refused with backup_state unless the backup is ready. A backup of another server or organisation is not found. Needs games.backups. State: integrated on fakes; the pre-restore copy's 72-hour removal, the quota and ownership of the staging directory and a real object store are not built or run.
Request body (JSON):
{
"confirm_name": "string"
}| Response | Meaning |
|---|
| 202 | Recorded |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/config
List the config files of a game server that the editor can open
The files the server's signed template declares as editable (`config_editors`), with the format each is checked against and whether a save needs a restart. `managed_keys` are keys the platform itself sets at every start (the template's `config_files`), which an edit to them does not survive. Needs games.files and a server whose node is reachable (the refusals of the file list apply). Built on the file manager's typed requests (GF-1), and integrated on fakes only: it has not run against a real container node.
| Response | Meaning |
|---|
| 200 | The editors |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/config/file
Open a config file of a game server with its saved versions
Returns the text, the `version` to send back when saving, and the saved versions (newest first, without their text). A file that does not exist yet comes back with `exists` false and empty text. A path the template does not declare is config_path_unknown (404). Opening is recorded in Activity by path and size, never content.
| Parameter | In | Notes |
|---|
path | query | |
| Response | Meaning |
|---|
| 200 | The file |
| default | Error |
PUT /api/v1/orgs/{org}/games/{game}/config/file
Save a config file of a game server after checking its syntax
Checks the text against the file's format first (json, yaml, properties and ini; `text` is not checked) and refuses a syntax error with validation_failed, naming the line in `fields.content`; nothing is written. Writes over the `version` that was opened (file_conflict, 409, if the file changed since; a file that does not exist is created and takes no version) through the file manager's atomic write, so the denylist, quota and ownership rules apply. What the file held before is kept first when no saved version is that text (as `baseline`, or `external` if it was changed elsewhere), then the new text is kept as a version; the newest 20 per file are kept. The text of a version is sealed, because a config file can hold a password. `restart_required` is the hint to restart the server for the change to apply. Saving the text the file already holds changes nothing (`changed` false). Recorded in Activity by path, size and the number of lines changed, never the text.
Request body (JSON):
{
"content": "string",
"path": "string",
"version": "string"
}| Response | Meaning |
|---|
| 200 | Saved |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/config/undo
Restore an earlier saved version of a config file
Writes a saved version back as the file, the same way a save does: over the `version` the editor last saw (file_conflict if it changed), keeping what is there first so the undo can itself be undone, and recording the restore as a new version of source `undo`. Recorded in Activity.
Request body (JSON):
{
"path": "string",
"version": "string",
"version_id": "00000000-0000-0000-0000-000000000000"
}| Response | Meaning |
|---|
| 200 | Restored |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/config/version
Read one saved version of a config file
The text of one saved version, to look at before restoring it. Another file's version, or another server's, is not found.
| Parameter | In | Notes |
|---|
path | query | |
id | query | |
| Response | Meaning |
|---|
| 200 | The version |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/console
Get a single-use token to open a game server's console
Returns a token valid for 60 seconds and one connection, and the edgegw WebSocket URL for the game server's text console: the container's output, and its standard input for the commands the person types. The browser opens `new WebSocket(ws_url, [protocol, token])`; the token travels in Sec-WebSocket-Protocol and never in a URL. edgegw verifies the token, refuses a second use, and checks the person's games.manage permission, the server and the caps again when the token is used and while the session lasts; the node verifies the token again. A new session starts with the recent output the node saw (not a durable log). What is typed or shown is never recorded. Refused with console_server_stopped (the server is not running), console_server_suspended, server_not_active (still being set up), server_busy (being created or removed), console_node_unreachable or console_session_limit (one session per server, four per person), and console_unavailable while the console is turned off. A server of another organisation, or a VPS, is not found. State: GC-1 is integrated on fakes only. On a real container runtime the console does NOT work yet: Containerd.Attach is read-only and reaches EOF immediately (it reads the existing log once, does not tail it, and refuses input), so it works only after GH-R4 (containerd console attach: tail the log + stdin).
Request body (JSON):
{}| Response | Meaning |
|---|
| 201 | Token minted; it is not stored and is shown only here |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/destroy
Destroy a game server
Ends the server and its billing and asks the node to remove it; when the node confirms, its address range and ports are given back. Needs the server's name in confirm_name and a recent step-up. Refused with server_not_active while the server is still being set up, and with server_protected while it is marked do-not-destroy. There is no trash: a destroyed game server's data is gone.
Request body (JSON):
{
"confirm_name": "string"
}| Response | Meaning |
|---|
| 202 | Terminating |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/eula
Show a game server's EULA and whether it is accepted
Returns the current EULA text for the server's game and whether this server has accepted that version, and by whom and when. Returns eula_unavailable when no text is published for the game. A server of another organisation is not found.
| Response | Meaning |
|---|
| 200 | The current EULA |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/eula
Accept a game server's current EULA
Records that the signed-in customer accepted the given EULA version for this server (who, organisation, time, version, text hash and address). The version must be the current one, otherwise eula_version_changed: read the new text and accept it. A new version needs a new acceptance before the next start. Accepting again is a no-op. A panel session only: an API token or a staff viewing session cannot accept.
Request body (JSON):
{
"version": 0
}| Response | Meaning |
|---|
| 200 | Accepted |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/files
List a folder of a game server
Lists the folder at `path` (default: the server's data directory). The node lists at most 5000 items: the first 5000 by name, then shown folders first within that page, so the page is the same each time; `truncated` says there were more. Every path is relative to the server's own data directory: `..`, `.`, backslashes and control characters are refused with file_path_invalid, and a leading slash is read as the data directory, never as the host. A symbolic link is listed as kind `link` and a device, pipe or socket as `special`; neither is ever followed or opened. Files the template denies (file_denylist) are not listed. Works while the server is stopped or running. Refused with files_node_unreachable (the host is away; nothing is lost), server_busy (being created, removed, installed, moved or restored), files_server_suspended, files_busy (two requests per server and six per organisation at once) or files_unavailable (turned off), files_node_unsupported (the host's vexd does not report the file capability, so nothing is sent to it). Anything that changes files, and anything that reads file content (read, download, archive), is also refused with files_unavailable when the template's file denylist cannot be found for the server; only this listing (names and sizes) still works. A server of another organisation is not found. Needs games.files; no API token scope opens it. State: GF-1 is integrated on fakes only; it has not run against a real container node.
| Parameter | In | Notes |
|---|
path | query | |
| Response | Meaning |
|---|
| 200 | The folder |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/files/archive
Download a file or folder of a game server as a zip
A zip of the file or folder at `path` ("" is the whole data directory) of at most 2000 entries, 16 MiB uncompressed and 2 MiB zipped (archive too large is file_too_large). Links, devices and denied files are left out. Recorded in Activity before any byte is sent.
| Parameter | In | Notes |
|---|
path | query | |
| Response | Meaning |
|---|
| 200 | The archive |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/files/content
Read a text file of a game server
Returns the file as text for the editor, with the `version` to send back when saving. Refused with file_too_large (413, more than 1 MiB; `fields.size` is its size) and not_text_file (415: not valid UTF-8 or holds control bytes; download it instead). A folder, link or device is refused (file_wrong_type, file_special); a denied file is file_denied; a missing one file_not_found. Opening a file is recorded in Activity by path and size (never content) before the content is returned. See the list route for the other refusals.
| Parameter | In | Notes |
|---|
path | query | |
| Response | Meaning |
|---|
| 200 | The file |
| default | Error |
PUT /api/v1/orgs/{org}/games/{game}/files/content
Save a text file of a game server
Writes the text atomically (a temporary file beside it, then a rename), so a failure leaves the original. Set `create` for a new file (refused with file_exists when it is there) or send the `version` that was read. A save with neither is refused (422): this route never overwrites a file it was not told the version of; a file that changed since is refused with file_conflict and nothing is written. The content must be text of at most 1 MiB (file_too_large, not_text_file). A write that would pass the plan's disk size is refused whole with file_disk_full. Recorded in Activity by path and size, never by content.
Request body (JSON):
{
"content": "string",
"create": false,
"path": "string",
"version": "string"
}| Response | Meaning |
|---|
| 200 | Saved |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/files/delete
Delete a file or folder of a game server
Deletes a file or an empty folder. A folder with contents needs `recursive: true` (folder_not_empty otherwise); a folder holding a denied file is not deleted. The data directory itself cannot be deleted. There is no trash yet: a deleted file is gone (the 7-day restore is not built). Recorded in Activity by path.
Request body (JSON):
{
"path": "string",
"recursive": false
}| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/files/download
Download a file of a game server
The file's bytes as an attachment (never rendered, never sniffed, never cached), at most 2 MiB; bigger files go by SFTP (file_too_large). The download is recorded in Activity before any byte is sent.
| Parameter | In | Notes |
|---|
path | query | |
| Response | Meaning |
|---|
| 200 | The file |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/files/mkdir
Create a folder in a game server
Creates one folder; its parent must exist (file_not_found) and it must not (file_exists).
Request body (JSON):
{
"path": "string"
}| Response | Meaning |
|---|
| 201 | Created |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/files/rename
Rename or move a file or folder of a game server
Moves a file or folder inside the data directory. It never replaces: a destination that exists is refused (file_exists). A folder cannot be moved into itself, and a folder holding a denied file is not moved.
Request body (JSON):
{
"from_path": "string",
"to_path": "string"
}| Response | Meaning |
|---|
| 200 | Moved |
| default | Error |
PUT /api/v1/orgs/{org}/games/{game}/files/upload
Upload a file to a game server
The body is the raw file with Content-Type application/octet-stream, at most 2 MiB (file_too_large; bigger files go by SFTP). It never replaces a file unless `overwrite=true` is explicit (file_exists); with it the file is replaced whatever its version, which is the upload's meaning. The folder it goes in must exist. The same atomic write, quota and denylist rules as saving apply.
| Parameter | In | Notes |
|---|
path | query | |
overwrite | query | |
| Response | Meaning |
|---|
| 201 | Stored |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/mods
List the mods and plugins a game server's template allows
The allow-list from the server's signed template (`mods`), each item with its state in the mods folder: `available`, `installed` (the file is there with the pinned size) or `different` (a file of that name is there with another size, which an install will not replace). `available` is false when the template declares none. A customer never supplies a URL: an item is installed by its id.
| Response | Meaning |
|---|
| 200 | The allow-list |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/mods/install
Install an allow-listed mod or plugin on a game server
Downloads the item from the URL the signed template names (never one the customer gives), refuses it unless its length and SHA-256 are exactly the pinned ones (mod_checksum_mismatch, 502; mod_fetch_failed, 502, when the download did not complete), and writes it into the mods folder through the file manager's atomic write, creating the folder if needed. The file must not exist with other contents (mod_file_present, 409); installing what is already there is a no-op. Items are at most 2 MiB, the file manager's transfer limit. The download uses the SSRF-hardened client and happens before any slot on the node is taken. Recorded in Activity by mod id, file name, version and size. A restart is needed for the game to load it (`restart_required`).
Request body (JSON):
{
"id": "string"
}| Response | Meaning |
|---|
| 200 | Installed, or already was |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/mods/remove
Remove an allow-listed mod or plugin from a game server
Deletes the file the item put in the mods folder. Only an item that is `installed` (the pinned name and size) is removed; a file of the same name with another size is the customer's own and is left (mod_not_installed, 409). Recorded in Activity.
Request body (JSON):
{
"id": "string"
}| Response | Meaning |
|---|
| 200 | Removed |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/power
Start, stop, restart or kill a game server
Queues one power action for the node and answers 202 with an operation_id. Start works from offline or crashed; stop from running or starting; kill from running, starting or stopping, and it also works while a stop or start is in flight, which it supersedes; restart stops a running server and starts it again when the stop is confirmed, and starts one that is not running. Start and restart are refused with eula_required until the current EULA version for the game is accepted for the server (see the eula route), and with eula_unavailable when no text is published for the game; stop and kill are never refused for that. Refused with server_busy while another action runs on the server, and with server_not_active when the server is not active (still being set up, suspended or failed) or is in a state that cannot do that. The state shown changes at once; the node confirms it. A server of another organisation, or a VPS, is not found.
Request body (JSON):
{
"action": "start"
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/reinstall
Reinstall a game server
Runs the game's installer again on a stopped server. Needs the server's name in confirm_name and a recent step-up. Reinstalling can replace game files and reset settings the installer owns: take a backup first (the server's own files are kept or replaced by the installer, never by this route). Refused with reinstall_needs_stopped unless the server is offline or crashed, server_busy while another action runs, server_not_active while it is still being set up, suspended or failed, and reinstall_unavailable when its host cannot reinstall yet (nothing is queued). The state shows installing at once and returns to offline when the host confirms; the outcome is reported in the server's reinstall field.
Request body (JSON):
{
"confirm_name": "string"
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/schedules
List a game server's schedules
The server's schedules, oldest first, with each one's next run, last run and its outcome (`last_status`: ok, failed, skipped or missed; `last_error_code`), the failures in a row, and what the plan allows. A schedule that fails `limits.failure_limit` runs in a row is switched off (`enabled` false) and says why in `last_error_code`; saving it again switches it back on. A server of another organisation is not found. Needs games.schedules; no API token scope opens it. State: integrated on fakes.
| Response | Meaning |
|---|
| 200 | The schedules |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/schedules
Create a schedule for a game server
Creates a schedule: a five-field cron expression (minute hour day-of-month month weekday; numbers, ranges, lists and steps) in an IANA time zone, and one to ten tasks run in order. A task is a power action (start, stop or restart), one console command line (up to 1,024 characters, sent as written) or a backup; each waits `offset_seconds` (0 to 3,600, at most six hours in all) after the one before finished, and the run stops at a failed task unless it sets `continue_on_failure`. A time that does not exist when clocks go forward runs once at the first moment after the gap; a repeated hour runs once. Runs must be at least five minutes apart. `missed_policy` says what happens if the worker was down at the due time: run_once makes the run up (once) if it is less than 30 minutes late, skip records it as missed; later than that it is always missed, never run. A server that is not active at run time skips the run and says so. A backup task while another backup is running is skipped with the reason backup_running. A schedule may hold only a task its author could do by hand (power and commands need games.manage, backups games.backups): refused with schedule_task_forbidden, and a task whose author has lost the permission fails when it runs (permission_revoked). The plan allows a number of schedules (schedule_limit_reached, schedules_not_included) and names are unique per server, ignoring case (schedule_name_taken). Needs games.schedules. State: integrated on fakes; nothing has run on a real node.
Request body (JSON):
{
"cron": "0 4 * * *",
"enabled": false,
"missed_policy": "run_once",
"name": "string",
"tasks": [
null
],
"tz": "Europe/Berlin"
}| Response | Meaning |
|---|
| 201 | Created |
| default | Error |
DELETE /api/v1/orgs/{org}/games/{game}/schedules/{schedule}
Delete a game server schedule
Switches the schedule off and removes it from the list; its past runs stay in the history the audit trail points to. A run in flight finishes. A schedule of another server or organisation is not found. Needs games.schedules.
| Response | Meaning |
|---|
| 204 | Deleted |
| default | Error |
PUT /api/v1/orgs/{org}/games/{game}/schedules/{schedule}
Change a game server schedule
Replaces the schedule's definition (the same fields and rules as creating one). Saving clears the failures in a row and switches a schedule that was switched off back on (when `enabled` is true), and makes the person who saved it the one it acts as: its tasks run with the permissions that person has when they run. A run in flight finishes with the tasks it started with, or fails as schedule_changed if the list changed under it. A schedule of another server or organisation is not found. Needs games.schedules.
Request body (JSON):
{
"cron": "0 4 * * *",
"enabled": false,
"missed_policy": "run_once",
"name": "string",
"tasks": [
null
],
"tz": "Europe/Berlin"
}| Response | Meaning |
|---|
| 200 | Saved |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/schedules/{schedule}/runs
List the runs of a game server schedule
The latest 20 runs, newest first, each with its due time, how it ended (running, ok, failed, skipped or missed, with `reason` such as server_not_active, worker_down, missed_policy or task_failed) and the outcome of each task (pending, running, ok, failed or skipped, with a curated `code`: not_running, server_busy, eula_required, permission_revoked, backup_running, backup_limit_reached, timeout and others). No operation or backup ids are shown. A schedule of another server or organisation is not found. Needs games.schedules.
| Response | Meaning |
|---|
| 200 | The runs |
| default | Error |
POST /api/v1/orgs/{org}/games/{game}/sftp
Get a short-lived SFTP credential for a game server
Issues a credential for this person, from this panel session, for this one server on its node: the user name encodes the server and the person, and the password is a signed token that is good for 10 minutes to log in (a session that began in time lasts as long as the person's right to the server). It is shown only here and stored nowhere, so there is nothing to look up; ask for another. Account passwords never reach a node. Refused with sftp_unavailable while SFTP is turned off, files_node_unreachable, server_busy or files_server_suspended. State: GF-1 issues the credential on fakes; the SFTP service that accepts it (vex-sftp) is not built.
Request body (JSON):
{}| Response | Meaning |
|---|
| 201 | Issued |
| default | Error |
GET /api/v1/orgs/{org}/games/{game}/usage
CPU, memory, disk, network and players of a game server
One series per metric over a time range, downsampled to the step, in the shape of a VPS server's usage. Samples are kept at
one-minute resolution for 30 days. A bucket with no sample is a gap: its value and max are null and samples is 0. It is never
zero. A gap can mean the server was off, the node was unreachable, or the reading was not available: the node reads CPU against
the server's own cap (cpu.max) and memory from its cgroup, the disk in use from its data directory (every ten minutes, and not at
all for a directory too large to measure cheaply), network at the server's routed interface once the game network exists on the
node, and players only from a game that answers a query (Minecraft Java). Network is bytes per second. Buckets start at multiples
of the step since the Unix epoch (UTC); the answer covers whole buckets. Value is the mean over the minutes that have a sample;
max is the largest per-minute value. A server of another org is a 404. Needs games.read; no API token scope opens it.
State: integrated on fakes; no real node has measured a real container.
| Parameter | In | Notes |
|---|
metric | query | A metric to return. Repeat or comma-separate. Every game metric when omitted |
from | query | Start of the range (RFC 3339). Default is 24 hours before to |
to | query | End of the range (RFC 3339) |
step | query | Bucket size in seconds, a multiple of 60 up to 86400. Default is the smallest that gives at most 600 points |
| Response | Meaning |
|---|
| 200 | Usage series |
| default | Error |
me
GET /api/v1/me
Current user
Returns the signed-in user: profile, whether email is verified, authenticator and passkey status, staff permissions, the time until which a step-up is valid, and any active impersonation.
| Response | Meaning |
|---|
| 200 | User |
| default | Error |
PATCH /api/v1/me
Update profile
Changes your display name (up to 80 characters).
Request body (JSON):
{
"name": "string"
}| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
GET /api/v1/me/audit
Your account activity
Lists activity on your own account, newest first. Pages with before and limit (default 50, at most 100).
| Parameter | In | Notes |
|---|
before | query | |
limit | query | |
| Response | Meaning |
|---|
| 200 | Events |
| default | Error |
DELETE /api/v1/me/deletion
Cancel a request to delete your account
Stops a pending request. Nothing has been erased during the grace period, so it needs no step-up. You are emailed. 404 when nothing is pending.
| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
GET /api/v1/me/deletion
Whether your account can be deleted, and any request
The pending request if there is one, the last one that ended, and what is in the way now (servers, money owed either way, disputes, holds, unpaid invoices, an organisation you solely own that has other members). `allowed` is true when nothing is.
| Response | Meaning |
|---|
| 200 | The state |
| default | Error |
POST /api/v1/me/deletion
Ask for your account to be deleted
Confirm with your sign-in email. Refused with 409 deletion_blocked, naming each reason in `fields`, while anything is in the way. Otherwise the request waits out the grace period (14 days by default), during which you can cancel, and is then carried out: your credentials, cards, SSH keys, API tokens and support conversations are deleted or anonymised; invoices, payments and tax records stay. Needs a recent step-up and a panel session. You are emailed.
Request body (JSON):
{
"confirm": "string"
}| Response | Meaning |
|---|
| 201 | The request |
| default | Error |
DELETE /api/v1/me/email-change
Cancel a waiting change of sign-in address
Withdraws the change waiting for confirmation. Its links stop working. 404 when none is waiting.
| Response | Meaning |
|---|
| 204 | Cancelled |
| default | Error |
GET /api/v1/me/email-change
Your sign-in address and any change waiting for confirmation
Your current sign-in address and, if you asked to change it and the new address has not confirmed yet, the address and when the request runs out. Panel session only.
| Response | Meaning |
|---|
| 200 | The address and any waiting change |
| default | Error |
POST /api/v1/me/email-change
Ask to change your sign-in email address
Needs a recent step-up and a panel session. Sends a confirmation link to the NEW address and a notice with a cancel link to the OLD one; nothing changes until the new address opens its link, within 24 hours. The answer is the same whether or not the new address already belongs to an account (no link is sent in that case, and the change can never be confirmed), so it cannot be used to find out who has one. A newer request replaces a waiting one. At most five an hour. When the change is confirmed every session is signed out and unused email links are voided; passkeys and the authenticator app stay.
Request body (JSON):
{
"new_email": "string"
}| Response | Meaning |
|---|
| 202 | Check both inboxes |
| default | Error |
GET /api/v1/me/export-downloads/{token}
Download an export with a link
Serves the zip for a valid link made for the signed-in person. An expired, altered or someone else's link is token_invalid. Sent with Cache-Control no-store; each download is audited.
| Response | Meaning |
|---|
| 200 | The archive |
| default | Error |
GET /api/v1/me/exports
Your data exports
Your own account exports, newest first, with their state and how many days a finished one can be downloaded. Panel session only.
| Response | Meaning |
|---|
| 200 | The exports |
| default | Error |
POST /api/v1/me/exports
Ask for a copy of your data
Queues an export of the personal data held about you (profile, organisations, sessions, support tickets, SSH keys, API token metadata, your own activity) as a zip of JSON files. It never holds a secret or another person's data. The worker builds it and emails you when it is ready. Needs a recent step-up, a panel session, and at most three a day; one at a time (export_open).
| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
GET /api/v1/me/exports/{export}
One data export
State, size, checksum and expiry of one of your exports. 404 for anyone else's.
| Response | Meaning |
|---|
| 200 | The export |
| default | Error |
POST /api/v1/me/exports/{export}/download-link
Get a short-lived download link for an export
Needs a recent step-up. Makes a link that works for five minutes, once, for you only in this session, and while the export is ready (export_not_ready otherwise). Making another link cancels the earlier one.
| Response | Meaning |
|---|
| 200 | The link |
| default | Error |
GET /api/v1/me/game-access
The game servers shared with me and the invitations waiting
The signed-in person's active grants and the invitations sent to their address that are still open (not expired), across organisations. Each says which server, whose organisation, which rights, and who invited them. Nothing else about the organisation is shown. State: integrated on fakes.
| Response | Meaning |
|---|
| 200 | Access given to me |
| default | Error |
DELETE /api/v1/me/game-access/{grant}
Decline an invitation or give up access to a game server
The person's own act, with no step-up: a pending invitation is declined (game.subuser_declined), an active grant ends (game.subuser_left) and a console they have open ends at the gateway's next heartbeat. Another person's grant is not found. State: integrated on fakes.
| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
POST /api/v1/me/game-access/{grant}/accept
Accept an invitation to a game server
Accepts a pending invitation sent to the signed-in person's verified address. Another account, an expired or withdrawn invitation and an unverified address are all invite_invalid (404). The inviter's rights are checked again now (invite_inviter_changed, nothing is accepted); that includes the inviter still being an active member whose role holds games.subusers, checked under the organisation lock. Audited as game.subuser_accepted. State: integrated on fakes.
| Response | Meaning |
|---|
| 200 | Accepted |
| default | Error |
GET /api/v1/me/mail-status
Whether email to your address is stopped
True when email to your sign-in address bounced for good or was marked as spam, so it is no longer sent.
| Response | Meaning |
|---|
| 200 | Mail status of your sign-in address |
| default | Error |
GET /api/v1/me/notification-preferences
Which emails you receive
Every kind of email, whether it is required (`locked`) and whether you receive it. Security and billing email is locked on.
| Response | Meaning |
|---|
| 200 | The preferences |
| default | Error |
PUT /api/v1/me/notification-preferences
Choose which optional emails you receive
Names the kinds to change and leaves the others as they are. Asking to switch off a locked kind (security, billing) is refused with 422 and nothing changes. Takes effect for email queued from now on.
Request body (JSON):
{
"preferences": {}
}| Response | Meaning |
|---|
| 200 | The preferences after the change |
| default | Error |
GET /api/v1/me/orgs
Organisations you belong to
Lists the organisations you belong to with your role in each. A viewing session started by staff sees only the organisation it was opened for.
| Response | Meaning |
|---|
| 200 | Orgs |
| default | Error |
GET /api/v1/me/passkeys
List passkeys
Lists your passkeys with their names, creation time and last use.
| Response | Meaning |
|---|
| 200 | OK |
| default | Error |
POST /api/v1/me/passkeys/begin
Start adding a passkey
Starts adding a passkey and returns the WebAuthn options with a challenge_id. Needs a recent step-up.
| Response | Meaning |
|---|
| 200 | Options |
| default | Error |
POST /api/v1/me/passkeys/finish
Finish adding a passkey
Finishes adding a passkey from the browser's WebAuthn response and the challenge_id from passkeys/begin. The optional name query parameter labels it. Needs a recent step-up.
| Parameter | In | Notes |
|---|
X-Vexelia-Challenge | header | |
name | query | |
Request body (JSON):
{}| Response | Meaning |
|---|
| 201 | Added |
| default | Error |
DELETE /api/v1/me/passkeys/{passkey}
Remove a passkey
Removes one of your passkeys. Needs a recent step-up. Returns not_found if the passkey is not yours.
| Parameter | In | Notes |
|---|
passkey | path | |
| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
POST /api/v1/me/password
Change password
Changes your password and signs out all your other sessions. Needs the current password (invalid_credentials) and a new one that meets the password policy (weak_password: length) and is not a very common password or one found in a known breach (password_breached; the breach-list lookup lets the password through when that service is down). A notice is emailed.
Request body (JSON):
{
"current_password": "string",
"new_password": "string"
}| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
POST /api/v1/me/recovery-codes
Regenerate recovery codes
Replaces your recovery codes with a new set, shown once. Needs a recent step-up. Returns conflict if the authenticator app is not set up.
| Response | Meaning |
|---|
| 200 | Codes |
| default | Error |
GET /api/v1/me/sessions
List signed-in sessions
Lists your signed-in sessions with IP address, browser and last activity, and marks the current one.
| Response | Meaning |
|---|
| 200 | OK |
| default | Error |
DELETE /api/v1/me/sessions/{session}
Sign out a session
Signs out one of your own sessions, which can be the current one (the session cookie is then cleared). Returns not_found if the session is not yours.
| Parameter | In | Notes |
|---|
session | path | |
| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
GET /api/v1/me/sso
Your linked identity providers
The providers on offer with whether each is linked to your account, and links to providers that are no longer offered (so they can still be removed). Also whether this is a staff account (which cannot use a provider) and whether you can sign in with a password. Never the provider's subject. Panel session only.
| Response | Meaning |
|---|
| 200 | Your links |
| default | Error |
DELETE /api/v1/me/sso/{provider}
Unlink an identity provider from your account
Needs a recent step-up and a panel session. Refused with sso_last_sign_in_method when it is the only way you can sign in (no password you can use, no passkey, no other provider that is switched on); the check runs under a lock on your account, so two unlinks at once cannot remove both. A mail tells you it happened.
| Response | Meaning |
|---|
| 204 | Unlinked |
| default | Error |
POST /api/v1/me/sso/{provider}/link
Start linking an identity provider to your account
Needs a recent step-up and a panel session. Returns the provider's authorization address; the provider asks you to sign in again (prompt=login) and the callback must return in this browser and this session. The identity is tied to this account whatever its email says, and only if no other account has it. Refused for staff accounts (sso_not_for_staff) and when this provider is already linked (sso_already_linked). A mail tells you it happened.
| Response | Meaning |
|---|
| 200 | Where to send the browser |
| default | Error |
POST /api/v1/me/step-up
Re-authenticate for sensitive actions
Re-authenticates with your password or authenticator code and returns the time until which sensitive actions are allowed (5 minutes). Wrong credentials return invalid_credentials.
Request body (JSON):
{
"code": "string",
"password": "string"
}| Response | Meaning |
|---|
| 200 | Window open |
| default | Error |
DELETE /api/v1/me/totp
Remove authenticator app
Turns off authenticator-app sign-in and deletes your recovery codes. Needs a recent step-up. A confirmation email is queued to your address.
| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
POST /api/v1/me/totp
Start authenticator app setup
Starts authenticator-app setup and returns the secret and otpauth URL. Needs a recent step-up. Returns conflict if an authenticator app is already confirmed. Setup completes with totp/confirm.
| Response | Meaning |
|---|
| 200 | Secret |
| default | Error |
POST /api/v1/me/totp/confirm
Confirm authenticator app and get recovery codes
Confirms authenticator-app setup with a current code and returns your recovery codes, shown once. Needs a recent step-up. A wrong code returns invalid_code.
Request body (JSON):
{
"code": "string"
}| Response | Meaning |
|---|
| 200 | Codes |
| default | Error |
GET /api/v1/me/transfers
Ownership offers made to you
Lists the open offers to take over an organisation that were made to you, with what you would take on: how many servers it has and how many payment methods stay on it. Expired offers are not listed.
| Response | Meaning |
|---|
| 200 | Offers |
| default | Error |
POST /api/v1/me/transfers/{transfer}/accept
Accept an ownership offer
Makes you the owner and the person who handed it over the role they chose, in one step. Needs a recent step-up and accept_billing: true, confirming you take over the organisation's billing: its renewals charge the payment methods and credit already on it until you change them. An offer that is expired, answered, or no longer the giver's to make returns 409 transfer_invalid. Someone else's offer is 404.
Request body (JSON):
{
"accept_billing": false
}| Response | Meaning |
|---|
| 200 | Accepted |
| default | Error |
POST /api/v1/me/transfers/{transfer}/decline
Decline an ownership offer
Turns the offer down; the owner is told and nothing changes.
| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
DELETE /api/v1/me/trusted-browsers
Remove every trusted browser
Removes all of your trusted browsers, so the authenticator code is asked at every password sign-in again, and clears this browser's trusted-browser cookie. Needs no step-up: it only takes a convenience away. Audited when anything was removed. Panel session only.
| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
GET /api/v1/me/trusted-browsers
Browsers you marked as trusted
Lists the browsers you marked as trusted when you signed in with your authenticator app code, newest first, with the browser name, the address it was marked from, when it was last used to skip the code and when the trust ends; the one you are using is marked current. enabled is false when the service has turned the feature off, and days is how long a new trust lasts. Panel session only.
| Response | Meaning |
|---|
| 200 | The trusted browsers |
| default | Error |
DELETE /api/v1/me/trusted-browsers/{browser}
Remove one trusted browser
Removes one of your trusted browsers; the next password sign-in from it asks for the authenticator code. If it is the browser making the request, its trusted-browser cookie is cleared too. Returns not_found if it is not yours. Needs no step-up. Audited. Panel session only.
| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
GET /api/v1/me/two-factor
Your second factor and which organisations require one
Whether you have a second factor (a confirmed authenticator app or a passkey), how many recovery codes are left, and which of your organisations require one, with each grace period. enrolment_required is true when you have none and a rule already holds you out; signin_required when you have one but this session did not use it (a password-only session of a person with a passkey is not compliant) (an organisation whose grace period ended, or the staff rule): turn one on before anything else works. must_keep says removing your last one would be refused. Panel session only. No secret is returned.
| Response | Meaning |
|---|
| 200 | Your state |
| default | Error |
servers
GET /api/v1/orgs/{org}/servers
List servers
Newest first, with the server id as tiebreak. Repeat the label query parameter to keep only servers that carry every label given (case does not matter). Without limit and cursor every matching server is returned; with them the list is cursor paged (limit 1 to 200) and next_cursor is the cursor for the next page, null on the last. An invalid cursor or limit is 422.
| Parameter | In | Notes |
|---|
label | query | |
limit | query | Page size, 1 to 200 (a larger value is lowered to 200). Without limit and cursor the whole list is returned, except where the operation says otherwise. |
cursor | query | The next_cursor of the previous page. Opaque; valid only for the list that issued it. |
| Response | Meaning |
|---|
| 200 | Servers |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
GET /api/v1/orgs/{org}/servers/{server}
Get a server
Returns one server: its plan, image, addresses, power state, lifecycle and any action still running. Poll it, or the operation it names, after a power, rebuild or firewall change.
| Response | Meaning |
|---|
| 200 | Server |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
GET /api/v1/orgs/{org}/servers/{server}/apps
The apps installed on a server and the apps it can install
The server's apps, newest first (installed, being installed, failed, and removed by a rebuild), each with its state, where it answers, where its credentials are, and the choices made; and the apps that may be installed on an existing server. A queued or running install the server has said nothing about for 45 minutes shows as unconfirmed. Answers not_found when the marketplace is switched off. Closed to API tokens.
| Response | Meaning |
|---|
| 200 | Apps |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/apps
Install an app on an existing server
Queues the install of one signed first-party app on a server that is already in use. Run the plan first: it shows every check and what will change. The server restarts once (confirm_restart must be true, app_restart_confirm_required otherwise); the host verifies the app's signature with its own key, generates every password itself and a Vexelia installer runs as root inside the server, refusing to run over files, packages, users or ports it did not create. Refused with 409 when the server is not running (app_server_not_running), is not Debian or Ubuntu (app_unsupported_os), the plan is too small (app_plan_too_small), the host cannot install apps (app_host_unsupported), the app is already installed (app_already_installed), another install runs (app_install_busy), a port is taken by another app (app_port_conflict), or a limit is reached (app_limit, app_too_many_failures, app_rate_limited); with 422 on inputs (including a password, which is never accepted). A repeat with the same Idempotency-Key returns the same install. Returns 202; progress is the app's state. Needs a verified email and step-up. Closed to API tokens.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | |
Request body (JSON):
{
"confirm_restart": false,
"inputs": {},
"recipe_id": "string"
}| Response | Meaning |
|---|
| 202 | Install queued |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/apps/plan
Check an app install on a server and show what it would change
A dry run: validates the choices exactly as the host will, checks the server (running, supported system and processor, enough disk and memory, a host that can install, no other install running, no port clash with the apps already there) and lists what would change, including the restart and whether a snapshot first is advised. Nothing is written and no host is asked. Every finding is in the answer, with ok false when any check failed. Answers 422 on recipe_id for an app that is not offered for existing servers.
Request body (JSON):
{
"inputs": {},
"recipe_id": "string"
}| Response | Meaning |
|---|
| 200 | The plan |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/apps/{app}
One app on a server and its install progress
The app, its state and where it answers. The state comes from the customer's own server, so treat it as a hint. An app of another server or organisation is not found.
| Response | Meaning |
|---|
| 200 | App |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/apps/{app}/abandon
Give up on an install that is not finishing
Ends a queued or running install: it becomes failed with the reason user_gave_up, leaves the live set (the app can be installed again) and releases the server (a resize or a restore is no longer held back). Nothing is undone on the server: what the installer already did stays. Refused with 409 app_install_running while the host reports the install running (its operation running, or progress in the last ten minutes) and 409 app_not_abandonable when it already ended. Installs that no host picks up are ended automatically after 2 hours, and ones silent for hours after the stale window. Needs step-up. Closed to API tokens.
| Response | Meaning |
|---|
| 200 | The ended install |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/apps/{app}/changes
Uninstall or update an app on an existing server
Queues the uninstall or the update of one installed app. Run the plan first. The server restarts once (confirm_restart must be true, app_restart_confirm_required otherwise); the host verifies the app's signature with its own key and a Vexelia script runs as root inside the server, removing only what the installer created or updating the app in place. Choosing to remove the app's data needs confirm_data_removal: true (app_data_removal_confirm_required otherwise) and the plan's data_digest echoed as data_digest (app_data_removal_changed when what would be deleted is no longer what the plan listed); keep_data defaults to true. The change carries no password or other secret. Refused with 409 when the app is not ready (app_change_not_ready), the recipe has no such step or the installed version is too old to update (app_change_unsupported), it is already at the newest version (app_up_to_date), another install or change holds the server (app_change_busy), the server is not running (app_server_not_running), the host cannot change apps (app_host_unsupported), or a limit is reached (app_too_many_failures, app_rate_limited). A repeat with the same Idempotency-Key returns the same change. Returns 202; progress is the change's state, and an uninstall that succeeds moves the app to removed, an update records the new version. Needs a verified email and step-up. Closed to API tokens.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | |
Request body (JSON):
{
"action": "uninstall",
"confirm_data_removal": false,
"confirm_restart": false,
"data_digest": "string",
"keep_data": false
}| Response | Meaning |
|---|
| 202 | Change queued |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/apps/{app}/changes/{change}
One uninstall or update of an app and its progress
The change, its state and the step it stopped at. The state comes from the customer's own server, so treat it as a hint. A change of another app, server or organisation is not found.
| Response | Meaning |
|---|
| 200 | Change |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/apps/{app}/changes/{change}/abandon
Give up on an uninstall or update that is not finishing
Ends a queued or running change: it becomes failed with the reason user_gave_up and releases the server (a resize or a restore is no longer held back). The app stays installed as the record had it; nothing is undone on the server, so what the script already did stays. Refused with 409 app_change_running while the host reports the change running (its operation running, or progress in the last ten minutes) and 409 app_change_not_abandonable when it already ended. Changes that no host picks up are ended automatically after 2 hours, and ones silent for hours after the stale window. Needs step-up. Closed to API tokens.
| Response | Meaning |
|---|
| 200 | The ended change |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/apps/{app}/credentials/reveal
Show an app's generated credentials once
Returns the passwords the host generated for this install and marks them shown. The first call gets them (Cache-Control: no-store); every later call is 409 credentials_viewed, and a call before the host delivered them is 409 app_no_credentials. Recorded in Activity by key count, never by value. Needs step-up. Closed to API tokens.
| Response | Meaning |
|---|
| 200 | The credentials |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/apps/{app}/plan
Check an uninstall or update of an app and show what it would change
A dry run for uninstalling or updating an installed app: checks the app (finished installing, the recipe still offered with the step you asked for, and for an update a newer version the recipe can update this install to), the server (active, running, not in rescue or on an ISO), the host (it reports it can change apps), that no install or other change holds the server, and the abuse limits. It says what goes whatever you choose (removes), the data that is kept or removed with your choice (data_choice, keep_data, data_kept, data_removed; the choice defaults to keeping the data), what an update changes (notes), what happens to the server (the restart and the root script; a failure part-way leaves what it did, and a snapshot taken first is the only way back) and whether a snapshot is advised. Nothing is written and no host is asked. An app of another server or organisation is not found.
Request body (JSON):
{
"action": "uninstall",
"keep_data": false
}| Response | Meaning |
|---|
| 200 | The plan |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/automation
A server's user-data, startup scripts and cloud-init switch
What the server is given on its first boot after a rebuild: the cloud-init user-data, the named startup scripts in order, the cloud-init switch, and the variables the text may use. The text may hold the caller's own secrets, so it needs servers.manage, is never cached, and never appears in the audit log. It runs as root on the first boot only; changing it does not touch a running server, it is used at the next rebuild.
| Response | Meaning |
|---|
| 200 | The server's automation |
| default | Error |
PUT /api/v1/orgs/{org}/servers/{server}/automation
Set a server's user-data or cloud-init switch
Sets the user-data (a #cloud-config document or a #! script, up to 64 KiB; #include is refused; a cloud-config that is not valid YAML is accepted with a warning) and/or the cloud-init switch. Variables are written ${vexelia.server.id}, ${vexelia.server.hostname}, ${vexelia.net.ipv4}, ${vexelia.net.ipv6_prefix} and ${vexelia.org.KEY}; any other ${vexelia.x} is refused naming it, and other ${...} (a shell's own) is left alone. Substitution happens once, so a value that contains ${vexelia.x} is not expanded again. The user-data and scripts together are at most 192 KiB.
Request body (JSON):
{
"cloud_init_enabled": false,
"user_data": "string"
}| Response | Meaning |
|---|
| 200 | The server's automation after the change |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/automation/scripts
Add a startup script
Adds a named script that starts with a
Request body (JSON):
{
"content": "string",
"name": "string",
"position": 0
}| Response | Meaning |
|---|
| 200 | The server's automation after the change |
| default | Error |
DELETE /api/v1/orgs/{org}/servers/{server}/automation/scripts/{script}
Delete a startup script
Deletes a startup script from the server. Returns not_found if the script does not exist on this server.
| Response | Meaning |
|---|
| 200 | The server's automation after the change |
| default | Error |
PUT /api/v1/orgs/{org}/servers/{server}/automation/scripts/{script}
Change a startup script
Replaces the script's name and text. Its position stays.
Request body (JSON):
{
"content": "string",
"name": "string"
}| Response | Meaning |
|---|
| 200 | The server's automation after the change |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/autopay
Turn automatic renewal on or off
Turns automatic renewal on or off for a server. Turning it on needs a saved card to charge (autopay_needs_card).
Request body (JSON):
{
"autopay": false
}| Response | Meaning |
|---|
| 204 | Done |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/backups
List a server's backups
The server's backups, newest first, and its backup requests that are waiting or were refused. A backup is ready once its manifest has been read back and checked; incomplete means the node stopped part way; unusable means a stored object failed its check and it is never restored from. A stopped server's last ready backup stands (power_state says whether the server is stopped). consistency is application when the guest agent froze the disk, crash otherwise.
| Response | Meaning |
|---|
| 200 | Backups |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/backups" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/backups
Back a server up now
Asks for a backup of the primary disk, or of the disks named, now. The worker starts it as soon as the server is free; it is incremental unless full is true or the server's disk was replaced, and a stopped server's backup is a full read of its disk. Limited to 2 requests per server in any 10 minutes (backup_cooldown, with retry_at), refused with backup_queued when that disk already has one waiting or running, and with server_not_active unless the server is active.
Request body (JSON):
{
"disks": [
null
],
"full": false
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
DELETE /api/v1/orgs/{org}/servers/{server}/backups/addon
Turn the Backups addon off at the end of the paid period
The addon keeps working until the end of what is paid for (ends_at) and the renewal that starts then no longer carries it. There is no credit for the rest of the period (decision 0008 applied to addons). Existing backups stay listed and restorable until retention removes them; no new manual or scheduled backup is made once the addon ends, and the schedule is switched off then. Repeating the call changes nothing; turning the addon on again before ends_at withdraws it at no charge. addon_not_enabled when the addon is not on; renewal_charging while the server's renewal is being paid.
| Response | Meaning |
|---|
| 200 | When the addon stops |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/backups/addon
Show a server's Backups addon and its price
Whether the Backups addon is on (state off, on, or ending: turned off, still working until ends_at), its price for one billing period at the price the next renewal uses (the greater of floor_minor_per_month times the months of the period and share_bp ten-thousandths of the plan's price for the period; both are staff settings), any open quote to pay, and the amount of a small charge deferred to the next renewal. Manual and scheduled backups need the addon (backup_addon_required without it); restoring and listing backups do not.
| Response | Meaning |
|---|
| 200 | The addon |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/backups/addon" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/backups/addon
Turn the Backups addon on
Prices the addon for the rest of the period the customer has paid for (remaining over total as an exact duration, rounded half up once; a renewal paid before its period began adds that whole period too), plus tax. The response carries it as charge, which stands for 30 minutes and is paid with POST .../backups/addon/charges/{charge}/pay (credit first, then card or 3-D Secure); the addon is on once the payment settles. When nothing is to be paid first (a zero charge, a charge below the card minimum that the billing policy defers to the renewal, a turn-off withdrawn before it took effect, or the addon already on) enabled is true. The renewal after the paid period bills the addon whole as a line of its own at the plan's price then (decision 0012). Refused with renewal_charging while the server's renewal is being paid, addon_charge_open while another quote is being paid, paid_period_ended when the paid period has ended, server_not_active unless the server is active. Answers 503 tax_unavailable while tax cannot be worked out yet for the buyer's location; nothing is created or charged, and trying again later succeeds once it is set up.
| Response | Meaning |
|---|
| 202 | A charge to pay first, or the addon already on |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/backups/addon/charges/{charge}/pay
Pay for turning the Backups addon on
Pays the addon's charge: wallet credit is held first, then one card intent covers the rest (the client secret is returned for Stripe.js), or the charge settles at once when credit covers it. Settlement issues an addon invoice, turns the addon on and voids the renewal that was issued without it. Repeating the call shows the same intent. quote_expired after 30 minutes (ask again), paid_period_ended if a renewal settled or the period ended since the quote, amount_below_minimum when the card part is below what the processor can charge. A payment that arrives after the quote can no longer be used is refunded to the card as a stray payment.
Request body (JSON):
{
"payment_method": "00000000-0000-0000-0000-000000000000"
}| Response | Meaning |
|---|
| 200 | Where the payment stands |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/backups/offsite
Show a server's off-site backup copy
Whether an off-site provider is connected (available), whether the weekly off-site copy is on for this server (decision 0019; on only while the Backups addon works), when it was turned on, when the last copy and the last monthly test restore succeeded, and the most recent runs (newest first, at most 12). A copy is an independent encrypted full image of the server's first disk kept with a second storage provider; a week with no succeeded run is a missed week. Listing needs no addon.
| Response | Meaning |
|---|
| 200 | The off-site copy |
| default | Error |
PUT /api/v1/orgs/{org}/servers/{server}/backups/offsite
Turn a server's off-site backup copy on or off
Opts the server in (enabled true) or out (enabled false) of the weekly off-site copy. Turning it on needs an off-site provider to be connected (offsite_unavailable, 409, when none is) and the Backups addon (backup_addon_required without it); turning it off never needs either, and repeating either call changes nothing and is not audited again. Existing copies are not deleted by turning it off. The copy itself is made later by the platform's worker, not by this call. Audited when the state changes.
Request body (JSON):
{
"enabled": false
}| Response | Meaning |
|---|
| 200 | The off-site copy after the change |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/backups/restore
Restore a backup over a server
Replaces the server's first disk with the backup chosen. The server in the path is the one restored onto; the backup may be one of another server of the same organisation (a backup of another organisation answers not_found). The server's current disk is kept in node trash for 72 hours, the server keeps its addresses, and the node checks the whole backup before it touches the disk (a damaged backup is refused with the server untouched). The server is stopped, its disk swapped, started, and must report running before the restore counts as done. Needs step-up and the server's hostname in confirm_hostname. Refused with server_not_active unless the server is active, restore_open when another restore onto it is waiting or running, and validation_failed when the backup is not of a first disk or the server's disk is smaller than the backup. To restore into a new server instead, order one with restore_backup_id (POST /orgs/{org}/orders).
Request body (JSON):
{
"backup_id": "00000000-0000-0000-0000-000000000000",
"confirm_hostname": "string"
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/backups/restore-point
Find the backup to restore for a time
Restore to a point in time. Backups are taken by schedule or on request with nothing stored between them, so the closest point is the newest ready backup of the server's first disk taken at or before the time in at (RFC 3339; a time in the future is read as now). The answer names the backup and taken_at, the time its disk image was frozen, so the customer sees what a restore will really bring back. Answers not_found when no ready backup that early is kept. Use the backup's id with the restore route, or as restore_backup_id on an order. Finer points between backups cannot be restored.
| Response | Meaning |
|---|
| 200 | The backup |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/backups/restore-point" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
GET /api/v1/orgs/{org}/servers/{server}/backups/restores
List restores onto a server
The latest restores requested onto this server. refusal says why a restore failed or was refused (server_busy, server_not_ready, server_changed, backup_unusable, corrupt, store_unavailable, post_restore_check_failed). mode is overwrite for a restore over this server's disk, and new_server for the restore a new-server order owes (order_id names the order). A new-server restore is refused with server_changed when the customer has rebuilt, resized, restored or otherwise worked on the new server before it started: the backup is then not put over that work.
| Response | Meaning |
|---|
| 200 | Restores |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/backups/restores" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
GET /api/v1/orgs/{org}/servers/{server}/backups/schedule
Show a server's backup schedule
Whether daily backups are on, the preferred start time and window in the chosen time zone, the allowed days (a bit mask, Monday 1 to Sunday 64), the extra disks included, the next three start times, and when the last run started or was missed. next_runs is empty while the schedule is off or paused; a suspended server's schedule pauses and resumes at the first window after it is active again. disks lists the disks that can be chosen.
| Response | Meaning |
|---|
| 200 | Schedule |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/backups/schedule" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
PUT /api/v1/orgs/{org}/servers/{server}/backups/schedule
Change a server's backup schedule
Turns daily backups on or off and sets the preferred start, window and days. The start is best effort; if the worker cannot start it inside the window the run shows as missed. Extra disks must be attached now. Refused with server_not_active when turning it on for a server that is not active.
Request body (JSON):
{
"allowed_days": 0,
"enabled": false,
"extra_disks": [
null
],
"tz": "string",
"window_minutes": 0,
"window_start_min": 0
}| Response | Meaning |
|---|
| 200 | Schedule |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/bandwidth
Traffic used this billing period and the quota
The traffic counted for the server in its current billing period, against the plan's quota. Periods
are monthly and begin on the day of the subscription's paid_until (the 31st clamps to the last day of
a short month). Counting is exact: the node reports monotonic byte counters with a sequence number, so
a report sent twice counts once, a counter that restarted counts from zero, and a node that was offline
and sends its backlog puts the bytes into the period they were used in.
direction says what counts against the quota: in, out, or both added together. An unlimited plan has
unlimited true and a null quota_bytes and percent. threshold is 0, 80 or 100, the share of the quota
that has been reached. warned_80_at and warned_100_at are when each notice was queued, once per period.
history lists the closed periods before this one, newest first.
A server of another org is a 404.
| Response | Meaning |
|---|
| 200 | Bandwidth this period |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/bandwidth" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/cancel
Cancel a server at the end of its paid period
Stops renewals (the open renewal invoice is voided and automatic renewal is turned off) and sets the server to be destroyed at paid_until. No refund is given for the unused part. Repeating it is a no-op. Refused with cancel_unavailable once the paid period has ended.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
| Response | Meaning |
|---|
| 200 | Cancelling |
| default | Error |
Example:
curl -X POST "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/cancel" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/cancel/undo
Undo a cancellation before the period ends
Puts the server back to normal. Automatic renewal stays off; the customer can turn it on again. Refused with not_cancelling when nothing is scheduled or the period has ended.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
| Response | Meaning |
|---|
| 200 | No longer cancelling |
| default | Error |
Example:
curl -X POST "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/cancel/undo" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/console
Get a single-use token to open a server's console
Returns a token valid for 60 seconds and one connection, and the edgegw WebSocket URL for the server's VNC or serial console. The browser opens `new WebSocket(ws_url, [protocol, token])`: the token travels in Sec-WebSocket-Protocol and never in a URL (a URL with a query string is refused). edgegw verifies the token, refuses a second use, and checks the person's permission, the server and the caps again when the token is used and for as long as the session lasts. Refused with console_server_stopped, console_server_suspended, console_rebuilding, console_node_unreachable or console_session_limit (one session per server and kind, four per person), and console_unavailable while the console is turned off. A closed WebSocket carries a close code in 4000-4999 naming the reason (token_expired, token_replayed, session_limit, vm_stopped, ended_by_staff, revoked, node_unreachable and others).
Request body (JSON):
{
"kind": "vnc"
}| Response | Meaning |
|---|
| 201 | Token minted; it is not stored and is shown only here |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/destroy
Destroy a server
Destroys the server through the trash, where it can be restored for 72 hours. Needs the server's name in confirm_hostname. Refused with server_protected while the server is marked do-not-destroy. A token needs the servers:destroy scope, an IP allowlist or an expiry under 30 days.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
Request body (JSON):
{
"confirm_hostname": "string"
}| Response | Meaning |
|---|
| 202 | Terminating |
| default | Error |
Example:
curl -X POST "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/destroy" \
-H "Authorization: Bearer $VEXELIA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"confirm_hostname":"string"}'POST /api/v1/orgs/{org}/servers/{server}/disk-charges/{charge}/pay
Pay for adding or growing an extra disk
Pays a disk charge: wallet credit is held first, then one card intent covers the rest (the client secret is returned for Stripe.js), or the charge settles at once when credit covers it. Settlement issues an invoice, reserves the disk (it is billed from then) and voids the renewal that was issued without it; the disk is then queued for the node. Repeating the call shows the same intent. quote_expired after 30 minutes (ask again), paid_period_ended if a renewal settled or the period ended since the quote, amount_below_minimum when the card part is below what the processor can charge.
Request body (JSON):
{
"payment_method": "00000000-0000-0000-0000-000000000000"
}| Response | Meaning |
|---|
| 200 | Where the payment stands |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/disks
A server's extra disks, with the plan's limits and price
The extra disks the server has now (being added, attached, growing or detaching) and what its plan allows. Prices are in minor units of the currency, per GiB per month; each disk renews at the plan's current per-GiB price under decision 0012 (a drop applies at once, a rise only if it took effect 30 days before the renewal). monthly_total_minor counts every disk that is paid for or attached (a disk being added or grown counts at its new size); it is added whole to each renewal.
| Response | Meaning |
|---|
| 200 | Disks and limits |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/disks" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/disks
Add an extra disk to a server
Prices a new blank disk of size_gib. The charge is the disk's price for the rest of the period the customer has paid for (remaining over total as an exact duration, rounded half up once; a renewal paid before its period began adds that whole period too), plus tax. The response carries it as charge, which stands for 30 minutes and is paid with POST .../disk-charges/{charge}/pay (credit first, then card or 3-D Secure); the disk is reserved, billed and queued for the node only after payment settles, and credited back to the wallet if the node cannot add it. When the charge is zero (a free price, or too little of the period left to round to a minor unit) the disk is queued at once and operation_id is set. The renewal after the paid period bills the disk whole at the plan's price (decision 0012). Refused with renewal_charging while the server's renewal is being paid, with disks_not_offered when the plan has none, disk_limit at the plan's count, node_full when the node has no room, disk_charge_open while another disk change is waiting to be paid or applied, paid_period_ended when the paid period has ended, server_busy while another action runs. A server defined before disks could be hot-plugged fails the operation with error_code no_hotplug_port; stop the server, add the disk, then start it. Answers 503 tax_unavailable while tax cannot be worked out yet for the buyer's location; nothing is created or charged, and trying again later succeeds once it is set up.
Request body (JSON):
{
"size_gib": 0
}| Response | Meaning |
|---|
| 202 | A charge to pay first |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/disks/{disk}/detach
Detach an extra disk
Takes the disk off the server; its file is kept in node trash for 72 hours. confirm_name must be the disk's name. The in-use check has limits. It asks the running guest's agent whether a filesystem on the disk is mounted. Use of a disk that is not mounted (raw database, LVM member, swap) is invisible to it, and a running server whose guest agent does not answer is refused unless force is true. A server that is not running can't be asked, so force is required for it too (409 disk_force_required). Forcing can lose data in the guest, and if /etc/fstab still lists the disk without the nofail option the server can drop into emergency mode at its next boot. A refusal by the node shows on the operation as error_code disk_in_use with an error_message. There is no credit for the rest of the paid period (decision 0008, VBD-1, applied to disks), so the period stands as paid and the next renewal no longer bills the disk. Refused with renewal_charging while the server's renewal is being paid.
Request body (JSON):
{
"confirm_name": "string",
"force": false
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/disks/{disk}/grow
Grow an extra disk
Prices growing the disk to size_gib, online if the server runs. The charge is the extra GiB at the price the disk already has, for the rest of the paid period (as for adding a disk), and is paid with POST .../disk-charges/{charge}/pay before the node is asked; the larger size is billed from payment, and credited back to the wallet if the node cannot grow it. A zero charge queues the grow at once. The guest sees the new size at once and grows its filesystem itself. A disk never shrinks, so size_gib must be larger than the disk is now and within the plan. Answers 503 tax_unavailable while tax cannot be worked out yet for the buyer's location; nothing is created or charged.
Request body (JSON):
{
"size_gib": 0
}| Response | Meaning |
|---|
| 202 | A charge to pay first |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/extension-cards
Extension cards for a server's page
The cards of every enabled customer manifest for the slot `customer.server.detail.aside`, resolved for this server of the caller's own organisation. A server of another organisation, or one that does not exist, is a 404 whatever manifests exist. Customer cards are stricter than staff cards: at most 2 cards of 6 blocks, short text with no typed web address, email address or markup, rows only from the platform's data source, and links only to the server or to support. The slot is empty unless the flag `ext_slots_customer_enabled` is on for the organisation (off by default). Read-only, so it also works in an impersonation session. Every string is plain text.
| Response | Meaning |
|---|
| 200 | The cards |
| default | Error |
DELETE /api/v1/orgs/{org}/servers/{server}/firewall
Detach the firewall policy from a server
Queues an apply_firewall operation that removes the customer firewall. Detaching when nothing is attached is a no-op. A terminated server can be detached too; nothing is sent to a node until it is restored. Detaching confirms a test that was running.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
Example:
curl -X DELETE "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/firewall" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
GET /api/v1/orgs/{org}/servers/{server}/firewall
Which firewall policy a server uses and whether it is applied
Returns the firewall attached to the server, how far the node is in applying it (apply_state), any test-mode countdown, and staff_reset when support has reset it to default-allow.
| Response | Meaning |
|---|
| 200 | Attachment |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/firewall" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
PUT /api/v1/orgs/{org}/servers/{server}/firewall
Attach a firewall policy to a server
One policy per server; attaching another replaces it. Queues an apply_firewall operation (follow operation_id at the operations route). A server that is busy with another action shows pending and is applied as soon as it is free. The policy must belong to the same organisation, else 404. With test_mode true the policy applies for two minutes and the node puts the previous firewall back by itself unless the change is kept (POST .../firewall/keep); the answer carries test_mode.expires_at. Refused with 409 firewall_lockout when the policy would newly block the caller's own SSH (the sentence is in fields.lockout) unless acknowledge_lockout is true, in test mode too. A policy filters the server's public interface only; when the server is on a private network the body must say private_network_ack true, else 409 private_network_firewall_ack_required, and every answer carries private_networks_unfiltered true.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
Request body (JSON):
{
"acknowledge_lockout": false,
"firewall_id": "00000000-0000-0000-0000-000000000000",
"private_network_ack": false,
"test_mode": false
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
Example:
curl -X PUT "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/firewall" \
-H "Authorization: Bearer $VEXELIA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"acknowledge_lockout":false,"firewall_id":"00000000-0000-0000-0000-000000000000","private_network_ack":false,"test_mode":false}'POST /api/v1/orgs/{org}/servers/{server}/firewall/keep
Keep the firewall applied in test mode
Tells the node to cancel its revert timer, so the firewall applied in test mode stays. The test ends in the database when the node confirms. 409 firewall_no_test when the server is not in test mode, firewall_test_over when the two minutes are up (the previous firewall is already back), server_busy while the test firewall has not reached the node yet.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
Example:
curl -X POST "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/firewall/keep" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/firewall/rollback
End test mode now and put the previous firewall back
Points the server at the firewall it had before the test and sends it as an ordinary change. 409 firewall_no_test when the server is not in test mode.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
Example:
curl -X POST "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/firewall/rollback" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/firmware
Change what a stopped server boots
Switches a server between UEFI and BIOS, or turns Secure Boot on or off. The server must be stopped, refused with server_not_stopped otherwise, and with server_busy while another action is running. The node updates the server's boot setup and leaves it stopped; the change applies when the server next starts. Omitted fields stay as they are, and turning UEFI off turns Secure Boot off. BIOS is refused on aarch64 servers, which always boot EFI, and each choice is refused when the server's operating system can't boot it (see boot_options on the server). The UEFI variable store, with the boot entry, is kept unless the change makes it unusable, which is switching Secure Boot or leaving UEFI.
Request body (JSON):
{
"firmware": "uefi",
"secure_boot": false
}| Response | Meaning |
|---|
| 202 | Queued; applies on the next start |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/hostname
Change a server's hostname
Writes the new hostname into a one-shot seed without restarting the server, so it applies at the server's next boot. Once the node has written it, the server shows it as pending_hostname; it becomes the hostname when the server next starts, restarts or has its password reset. A later change before then replaces it, and a password reset carries it.
Request body (JSON):
{
"hostname": "string"
}| Response | Meaning |
|---|
| 202 | Queued; applies on next reboot |
| default | Error |
PUT /api/v1/orgs/{org}/servers/{server}/labels
Replace a server's labels
Replaces the whole set. Up to 20 labels of 1 to 40 characters; blanks are dropped and a label repeated in any case is kept once. Labels are the customer's own and are not shown to other organisations. The server list filters by them.
Request body (JSON):
{
"labels": [
null
]
}| Response | Meaning |
|---|
| 200 | The labels now |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/media
Show the ISO mounted on a server
What the node last confirmed. iso is null when nothing is mounted; boot is the boot order (cdrom first or disk first); server_arch is the CPU architecture of the node group the server runs on, which a mounted ISO must match.
| Response | Meaning |
|---|
| 200 | Mounted media |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/media" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/media
Mount an ISO on a server and set its boot order
Mounts a ready ISO from the organisation's library or the system library on the server's CD-ROM, replacing any mounted one, with the boot order given. Another organisation's ISO is refused as if it did not exist. An ISO built for another CPU architecture than the server's is refused with a field error on iso_id. It applies at the server's next boot; the server is not restarted. Refused with server_not_active unless the server is active, and server_busy while another action runs.
Request body (JSON):
{
"boot": "cdrom",
"iso_id": "00000000-0000-0000-0000-000000000000"
}| Response | Meaning |
|---|
| 202 | Queued; applies at the next boot |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/media/unmount
Unmount the ISO from a server
Takes the ISO off the server and puts the disk first again. A running server has the disc ejected now; if QEMU reports the tray locked by the guest the operation fails with error code media_locked and changes nothing, unless force is true. That refusal is only proved against fakes. On the real QEMU of the nested aarch64 lab a guest-locked tray did not refuse a plain unmount, so do not rely on media_locked appearing. With nothing mounted it changes nothing.
Request body (JSON):
{
"force": false
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/network
A server's addresses, port speed and traffic
The server's IPv4 addresses (the first is primary) and IPv6 /64, each with the gateway the guest routes through, the plan's port speed, and the traffic seen in the last 30 days. Traffic is an estimate from the one-minute usage samples, not the billing meter: minutes with no sample count for nothing, and covered_minutes says how many minutes had one. in_bytes and out_bytes are null when there are no samples. assigning is true while a server being set up has no address yet. Each address carries its reverse DNS: the record in force (rdns), the newest request that is pending or was declined (rdns_request) and whether the record is forward-confirmed for mail (mail_eligible, null when there is no record; rdns_problem says what failed on the last check). The IPv6 range lists the addresses the guest uses and whether IPv6 is on; resolvers and network_apply carry the resolver choice and whether it has reached the node.
| Response | Meaning |
|---|
| 200 | The network tab |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/network" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
PUT /api/v1/orgs/{org}/servers/{server}/network
Choose a server's IPv6 addresses and resolvers
Saves the whole choice: IPv6 on or off, the addresses inside the server's /64 it may use (at most 16; empty means the whole range with its first address primary), the one the guest configures, and the resolvers it is told (at most 4; empty means the platform's default). The node replaces the server's anti-spoofing allowlist with exactly these addresses and writes a new network seed. The server keeps running and takes the seed at its next restart, so the answer says applies: next_restart and never that it is applied; the network tab then shows network_apply as pending, applied or failed. A refused choice keeps the stored one. Refused with server_busy while another action is running.
Request body (JSON):
{
"ipv6_addresses": [
null
],
"ipv6_enabled": false,
"primary_ipv6": "string",
"resolvers": [
null
]
}| Response | Meaning |
|---|
| 202 | Queued; applies on the server's next restart |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/password
Reset a server's root password
Generates a new root password and returns it once, in this response. It is never stored or logged; the node gets only a hash, through a one-shot seed, and the server restarts to apply it. Refused with server_busy while another action is running.
Request body (JSON):
{}| Response | Meaning |
|---|
| 202 | Queued; the password is shown only here |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/plan-change
Quote an upgrade or schedule a downgrade
A dearer plan is quoted as an upgrade (202): the response carries the quote, which stands for the policy's quote_minutes and is paid with POST plan-change/{change}/pay (credit first, then card or 3-D Secure). The node is asked to resize only after payment settles, and the server restarts once. A cheaper plan is scheduled for the next renewal (200) and gives no credit. Asking again for the same upgrade while its quote stands returns the same quote; another plan replaces the quote. Refused with plan_no_room when the host has no room for the larger plan (before any payment), plan_disk_shrink for a smaller disk, plan_change_open while a paid upgrade is being applied, plan_too_late when too little of the period is left to charge for, paid_period_ended after the paid period, downgrades_disabled when staff switched scheduling off, server_not_active for a server that is not running or is set to cancel, plan_other_class when the plan has another CPU class (shared or dedicated vCPUs) than the server, and plan_other_arch when it runs on another processor type (x86 or Arm). A server never changes class or architecture in a resize: such plans are listed as unavailable with reason other_class or other_arch, and moving to one means ordering a new server on it and moving the data. Answers 503 tax_unavailable while tax cannot be worked out yet for the buyer's location; nothing is created or charged, and trying again later succeeds once it is set up.
Request body (JSON):
{
"plan_id": "00000000-0000-0000-0000-000000000000"
}| Response | Meaning |
|---|
| 200 | A downgrade scheduled for the next renewal |
| 202 | An upgrade quoted for payment |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/plan-change/cancel-downgrade
Withdraw a scheduled downgrade
Withdraws a downgrade scheduled for the next renewal. A renewal already invoiced at the lower price is issued again at the current plan's price. canceled is false when there was none, including when the renewal at the lower price has already settled.
| Response | Meaning |
|---|
| 200 | Whether one was withdrawn |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/plan-change/{change}/pay
Pay for an upgrade
Pays an upgrade quote: wallet credit is held first, then one card intent covers the rest (the client secret is returned for Stripe.js), or the upgrade settles at once when credit covers it. Settlement issues an upgrade invoice and moves the subscription to the new plan's price; the worker then asks the node to resize the server, and credits the whole amount back to the wallet (never the card) if the node cannot. Repeating the call shows the same intent. quote_expired after the quote's time (ask again), paid_period_ended if a renewal settled since, amount_below_minimum when the card part is below what the processor can charge.
Request body (JSON):
{
"payment_method": "00000000-0000-0000-0000-000000000000"
}| Response | Meaning |
|---|
| 200 | Where the payment stands |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/plan-options
The plans a server can move to, with the price of an upgrade
Every VPS plan on sale for the server's billing period, each classified against the plan the server is on. An upgrade (dearer, and no fewer CPUs or less memory) is priced for now: the share of the paid period that remains, applied to the whole-period price difference, rounded half up once, plus tax. The page shows a preview; asking for the change (POST plan-change) stores the quote for the policy's quote_minutes. A downgrade takes effect at the next renewal and gives no credit for the rest of this period (decision 0008, VBD-1). A plan with a smaller disk is unavailable with reason disk_shrink (a disk never shrinks: rebuild onto the smaller plan). blocked says why nothing can be changed now: not_active, cancelling, or upgrade_in_progress.
| Response | Meaning |
|---|
| 200 | The plans and where any change stands |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/plan-options" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/power
Start, stop or restart a server
Queues a start, shutdown, stop or reboot and answers 202 with an operation_id. Refused with server_busy while another action runs on the server.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
Request body (JSON):
{
"action": "start"
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
Example:
curl -X POST "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/power" \
-H "Authorization: Bearer $VEXELIA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"start"}'PUT /api/v1/orgs/{org}/servers/{server}/protect
Mark a server do-not-destroy, or lift the mark
While a server is protected, destroying it, the end of a cancellation and a staff terminate are all refused with server_protected. The mark does not stop a server being suspended for non-payment. Lifting the mark needs a recent step-up (answered step_up_required without one); setting it does not. Repeating the same value is a no-op (changed is false). A token can never call this.
Request body (JSON):
{
"protected": false
}| Response | Meaning |
|---|
| 200 | The flag now |
| default | Error |
PUT /api/v1/orgs/{org}/servers/{server}/rdns
Ask for reverse DNS on one of a server's addresses
Asks for the reverse DNS name of one address of the server (an IPv4 address, or an IPv6 address the server uses). The name must already resolve to the address, an A or AAAA record, or the request is refused with rdns_not_confirmed: reverse DNS only counts for mail when it is forward-confirmed. A null or empty name asks to remove the record. The request goes to a staff queue labelled manual, because no provider reverse-DNS API is connected in the beta; staff set it at the provider and mark it done, and the network tab shows it as pending until then. The record is checked again every day and loses its mail flag, with an email to the owners and billing members, if the forward record goes away. A newer request for the same address replaces a pending one.
Request body (JSON):
{
"address": "string",
"name": "string"
}| Response | Meaning |
|---|
| 202 | Queued for staff |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/rebuild
Rebuild a server onto another image
Destroys everything on the server's disk and installs the chosen image again with the chosen SSH keys. Addresses, plan and billing are unchanged. confirm_name must be the server's name. Refused with rebuild_unavailable unless the server is active, and with server_busy while another action is running. If the chosen image can't boot the server's current firmware, the rebuild switches it. An image without UEFI switches the server to BIOS and turns Secure Boot off, an image without BIOS switches it to UEFI, and an image without Secure Boot turns it off (see supports_uefi, supports_bios and supports_secure_boot on the catalog). The old disk is kept in node trash for 72 hours. The recorded image, keys and hostname change when the node confirms.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
Request body (JSON):
{
"confirm_name": "string",
"hostname": "string",
"image_id": "00000000-0000-0000-0000-000000000000",
"ssh_key_ids": [
null
],
"timezone": "string",
"user_data": "string"
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
Example:
curl -X POST "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/rebuild" \
-H "Authorization: Bearer $VEXELIA_TOKEN" \
-H "Content-Type: application/json" \
-d '{"confirm_name":"string","hostname":"string","image_id":"00000000-0000-0000-0000-000000000000","ssh_key_ids":[null],"timezone":"string","user_data":"string"}'GET /api/v1/orgs/{org}/servers/{server}/rescue
Show a server's rescue mode
in_rescue while the server runs the rescue system (its lifecycle is rescue); until is when it goes back to its own disk automatically. available is false when no rescue system exists for the server's architecture.
| Response | Meaning |
|---|
| 200 | Rescue state |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/rescue" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/rescue
Boot a server into the rescue system
Stops the server and boots the signed Vexelia rescue system with the server's own disk attached as a second disk, untouched. The rescue system takes the server's SSH keys and the one-time root password returned here, which is shown only in this response and never stored. The rescue image's signature is checked on the node before the server is stopped. The server returns to its own disk after the staff-set maximum (24 hours by default), with an email an hour before. Refused with server_not_active unless the server is active, server_busy while another action runs, and rescue_unavailable when there is no rescue system for its architecture.
Request body (JSON):
{}| Response | Meaning |
|---|
| 202 | Queued; the password is shown only here |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/rescue/exit
Boot a server from its own disk again
Stops the rescue system and starts the server from its own disk, keeping whatever was changed on it. The rescue system's disk is deleted. Refused with not_in_rescue unless the server is in rescue.
| Parameter | In | Notes |
|---|
Idempotency-Key | header | Optional with an API token: a new string (1 to 100 printable ASCII characters) per logical request. Repeating the request with the same key returns the first answer and does not repeat the action (response header Idempotent-Replay: true); the same key with a different request is 422 idempotency_key_reused. Kept 24 hours, per token and route. |
Request body (JSON):
{}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
Example:
curl -X POST "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/rescue/exit" \
-H "Authorization: Bearer $VEXELIA_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'GET /api/v1/orgs/{org}/servers/{server}/snapshots
A server's snapshots
The server's snapshots, newest first, with the price, the limits, and open_charge, the snapshot charge waiting to be paid, if any (quoted and not yet expired; pay it with POST .../snapshots/charges/{charge}/pay until expires_at).
| Response | Meaning |
|---|
| 200 | The server's snapshots |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/snapshots" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/snapshots
Take a snapshot of a server
Prices a snapshot named name of the server's primary disk. The charge is the snapshot's price for the rest of the period the customer has paid for (remaining over total as an exact duration, rounded half up once; a renewal paid before its period began adds that whole period too), plus tax. The response carries it as charge, which stands for 30 minutes and is paid with POST .../snapshots/charges/{charge}/pay (credit first, then card or 3-D Secure); the snapshot is reserved, billed and queued for the node only after payment settles, and credited back to the wallet if the node cannot take it. A charge below the minimum a card can be charged is added to the next renewal invoice instead (deferred is true and the snapshot is queued at once), and a charge of zero queues the snapshot at once (operation_id is set). The renewal after the paid period bills the snapshot whole. Asking again for the same name while the quote stands returns the same quote. Refused with snapshot_name_taken when a live snapshot of the server has the name, snapshot_limit and snapshot_server_limit at the limits, renewal_charging while the server's renewal is being paid, snapshot_charge_open while another snapshot is waiting to be paid or taken, server_not_active when the server or its node is not available, paid_period_ended when the paid period has ended, server_busy while another action runs. Answers 503 tax_unavailable while tax cannot be worked out yet for the buyer's location; nothing is created or charged.
Request body (JSON):
{
"name": "string"
}| Response | Meaning |
|---|
| 202 | A charge to pay first, or the operation when there was nothing to pay |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/snapshots/charges/{charge}/pay
Pay for taking a snapshot
Pays a snapshot charge: wallet credit is held first, then one card intent covers the rest (the client secret is returned for Stripe.js), or the charge settles at once when credit covers it. Settlement issues an invoice, reserves the snapshot (it is billed from then) and voids the renewal that was issued without it; the snapshot is then queued for the node. Repeating the call shows the same intent. quote_expired after 30 minutes (ask again), paid_period_ended if a renewal settled or the period ended since the quote, amount_below_minimum when the card part is below what the processor can charge.
Request body (JSON):
{
"payment_method": "00000000-0000-0000-0000-000000000000"
}| Response | Meaning |
|---|
| 200 | Where the payment stands |
| default | Error |
DELETE /api/v1/orgs/{org}/servers/{server}/snapshots/{snapshot}
Delete a snapshot
Deletes a ready snapshot (queued as one operation) or a failed one (at once; operation_id is then null). There is no credit for the rest of the paid period (decision 0008 applied to snapshots), so the period stands as paid and the next renewal no longer bills it. Refused with snapshot_busy while the snapshot is being taken, restored or deleted, and with server_busy while another action runs on the server.
| Response | Meaning |
|---|
| 202 | Queued, or done for a failed snapshot |
| default | Error |
POST /api/v1/orgs/{org}/servers/{server}/snapshots/{snapshot}/restore
Restore a snapshot over a server's disk
Puts the snapshot over the server's primary disk: everything written since the snapshot was taken is replaced, and the server is restarted if it was running. confirm_hostname must be the server's hostname. Refused with server_busy while any other action runs on the server (a restore takes the whole disk, so it waits for backups too), snapshot_busy while the snapshot is not ready (being taken, restored or deleted), snapshot_server_changed when the server's disk is no longer the one the snapshot was taken of (it was rebuilt, restored from a backup or from trash, or changed size since), and server_not_active when the server is not active. The restore is queued as one operation; the snapshot reads restoring until the node answers and ready again after, with last_restored_at set when it worked.
Request body (JSON):
{
"confirm_hostname": "string"
}| Response | Meaning |
|---|
| 202 | Queued |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/support-grants
Support access given for a server
The most recent grants for the server, open and closed, newest first. status is active, expired or withdrawn. A server of another org is a 404.
| Response | Meaning |
|---|
| 200 | Grants |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/support-grants" \
-H "Authorization: Bearer $VEXELIA_TOKEN"
POST /api/v1/orgs/{org}/servers/{server}/support-grants
Give support staff time-limited access to a server
The customer's consent for support staff to work on this one server. scope is view, power or console, each
including the ones before it. hours is 1 to 168 (seven days), 24 when left out. ticket_id optionally links one
of the organisation's tickets. One grant is in force at a time; a second is refused with support_grant_active
until the first is withdrawn or ends. The grant is audited and shown to staff on the service. It does not stop
staff acting under their own permissions: it records that the customer agreed.
Request body (JSON):
{
"hours": 0,
"note": "string",
"scope": "view",
"ticket_id": "00000000-0000-0000-0000-000000000000"
}| Response | Meaning |
|---|
| 201 | Given |
| default | Error |
DELETE /api/v1/orgs/{org}/servers/{server}/support-grants/{grant}
Withdraw support access to a server
Ends the grant at once. Withdrawing a grant that is already withdrawn or expired is a no-op. A grant of another server or org is a 404.
| Response | Meaning |
|---|
| 204 | Withdrawn |
| default | Error |
GET /api/v1/orgs/{org}/servers/{server}/usage
CPU, RAM, disk, network and IOPS of a server
One series per metric over a time range, downsampled to the step. Samples are kept at one-minute
resolution for 30 days. A bucket with no sample is a gap: its value and max are null and samples is 0.
It is never zero. A gap can mean the server was off, the node was unreachable, or the reading was not available.
Buckets start at multiples of the step since the Unix epoch (UTC), and the answer covers whole buckets.
Value is the mean over the minutes that have a sample; max is the largest per-minute value.
Network is bytes per second and IOPS is reads and writes together per second. Disk is bytes in use as the
node measures it (allocated on the node unless a guest agent reports more).
A server of another org is a 404.
| Parameter | In | Notes |
|---|
metric | query | A metric to return. Repeat or comma-separate. All metrics when omitted |
from | query | Start of the range (RFC 3339). Default is 24 hours before to |
to | query | End of the range (RFC 3339) |
step | query | Bucket size in seconds, a multiple of 60 up to 86400. Default is the smallest that gives at most 600 points |
| Response | Meaning |
|---|
| 200 | Usage series |
| default | Error |
Example:
curl -X GET "$VEXELIA_URL/api/v1/orgs/$org/servers/$server/usage" \
-H "Authorization: Bearer $VEXELIA_TOKEN"