API reference · Using the API / Errors
Errors
Every error is a problem document with a stable code. Branch on the code, show the detail, and retry only what is worth retrying.
The shape of an error
Every error is application/problem+json (RFC 9457):
{
"type": "https://api.pantechdynamics.com/problems/insufficient-credit",
"title": "Insufficient credit",
"status": 402,
"code": "INSUFFICIENT_CREDIT",
"detail": "This instance needs NGN 15,040.00 of credit upfront and NGN 2,000.00 is available. Top up your credit or add a card, then try again.",
"request_id": "req_6Yq8mB2x0dKpL4vS"
}codeis the stable, machine-readable reason. Branch on it. It never changes within v1.detailexplains this occurrence for a person. Show it, log it, but do not parse it, because its wording can change.request_ididentifies the request. Quote it to support.
request_id is also in every response's X-Request-Id header, errors or not, so log it with each call you make.
A validation error lists every bad field in errors, so you can fix them all at once:
{
"status": 422,
"code": "VALIDATION_FAILED",
"title": "Validation failed",
"detail": "plan_slug: must identify an active plan.",
"request_id": "req_6Yq8mB2x0dKpL4vS",
"errors": [
{ "field": "plan_slug", "code": "PLAN_NOT_FOUND", "message": "must identify an active plan" }
]
}Its detail names each bad field and what it must be (field: message, separated by ;), so it is useful on its own. Show each entry of errors beside its field, and branch on its code.
By status
| Status | Means | Retry? |
|---|---|---|
400 | The request could not be read | No, fix it |
401 | No valid API key | No, fix the key |
402 | Not enough credit to pay | After topping up |
403 | The key may not do this | No |
404 | Nothing with that id in your organization | No |
409 | Not possible in the resource's current state | Sometimes; see the code |
413 | Body over 64 KiB | No |
422 | A field is invalid; see errors | No, fix it |
429 | Rate limited | Yes, after Retry-After |
500, 503 | Our side failed | Yes, with backoff and the same Idempotency-Key |
Request and authentication codes
code | Status | What to do |
|---|---|---|
INVALID_REQUEST_BODY | 400 | Send valid JSON |
IDEMPOTENCY_KEY_REQUIRED | 400 | Add an Idempotency-Key header |
INVALID_IDEMPOTENCY_KEY | 400 | Keep the key to 255 characters |
INVALID_FILTER | 400 | Use a listed filter value |
INVALID_CURSOR | 400 | Start the list again without a cursor |
INVALID_PAGE | 400 | Send a limit from 1 to 100, or leave it out for 50 |
INVALID_CALLER_ADDRESS | 400 | Retry from a normal internet connection |
UNAUTHENTICATED | 401 | Send a valid, unexpired key, and check the URL starts with /public/v1 |
INSUFFICIENT_SCOPE | 403 | Use a key with write scope |
FORBIDDEN | 403 | The key's creator lacks the role for this |
RESOURCE_NOT_FOUND | 404 | Check the id and the organization |
NOT_FOUND | 404 | The feature is not offered yet |
ROUTE_NOT_FOUND | 404 | Check the method and path |
REQUEST_TOO_LARGE | 413 | Send a smaller body |
VALIDATION_FAILED | 422 | Fix each field in errors |
RATE_LIMITED | 429 | Wait Retry-After seconds |
INTERNAL | 500 | Retry with backoff; quote request_id if it persists |
Account and billing codes
code | Status | What to do |
|---|---|---|
INSUFFICIENT_CREDIT | 402 | Top up credit or add a card in the console |
ACCOUNT_NOT_ACTIVE | 409 | Finish activating billing in the console |
ACCOUNT_SUSPENDED | 409 | Pay the overdue invoice; nothing new can be created until then |
PROVIDER_ACCOUNT_NOT_READY | 409 | Your account is still being set up; retry in a minute |
STATIC_NAT_LIMIT_EXCEEDED | 403 | Your organization has as many one-VM public IPs as it may. Release one, or contact support for a higher limit |
PUBLIC_IP_LIMIT_EXCEEDED | 403 | Your organization has as many public IPs, of any purpose, as it may (20 by default). Release one, or contact support for a higher limit |
CREDIT_PURCHASE_UNAVAILABLE | 409 | Finish billing setup in the console |
EMAIL_VERIFICATION_REQUIRED | 409 | The key's creator must verify their email first |
AMOUNT_OUT_OF_RANGE | 422 | Buy an amount within the per-purchase limits |
BILLING_UNAVAILABLE | 503 | Retry in a few seconds |
Resource state codes
These mean the request was fine but the resource cannot do it now.
code | Status | What to do |
|---|---|---|
INVALID_RESOURCE_STATE | 409 | Wait for the resource to settle, then retry |
CONCURRENT_UPDATE | 409 | Retry the same request with the same key |
IDEMPOTENCY_CONFLICT | 409 | Use a new Idempotency-Key for a different request |
INSTANCE_NAME_TAKEN | 409 | Choose another name |
INSTANCE_MUST_BE_STOPPED | 409 | Stop the instance first |
INSTANCE_HAS_PUBLIC_IP | 409 | A public IP still points at it. Detach the address to keep it, or release it, first |
INSTANCE_HAS_PORT_FORWARDS | 409 | Delete its port forwards first |
INSTANCE_ALREADY_HAS_PUBLIC_IP | 409 | It has a static NAT address already; detach or release that one first |
CONSOLE_INSTANCE_NOT_RUNNING | 409 | Start the instance first |
SSH_ACCESS_INSTANCE_NOT_RUNNING | 409 | Start the instance first |
SSH_ACCESS_SECURITY_GROUP_SHARED | 409 | Give the instance its own security group |
SSH_ACCESS_NOT_SUPPORTED_IN_ZONE | 409 | Not available for VPC instances yet |
SECURITY_GROUPS_NOT_SUPPORTED_IN_ZONE | 409 | VPC instances use firewall rules |
DEFAULT_SECURITY_GROUP_UNDELETABLE | 409 | Edit its rules instead |
FIREWALL_RULES_NOT_SUPPORTED | 409 | Firewall rules are for VPC subnets |
NETWORK_NAME_TAKEN | 409 | Choose another name |
NETWORK_NOT_READY | 409 | Wait for the VPC to be active |
NETWORK_HAS_INSTANCES | 409 | Delete its instances first |
NETWORK_HAS_PUBLIC_IPS | 409 | Release its public IPs first |
SUBNET_HAS_KUBERNETES_CLUSTERS | 409 | Delete the Kubernetes clusters in the subnet first |
PUBLIC_IP_NOT_FOR_PORT_FORWARDING | 409 | Use a port_forwarding address |
PUBLIC_IP_NOT_STATIC_NAT | 409 | Only a static_nat address can be attached or detached; change a port forwarding address's rules, or a load balancer's targets, instead |
PUBLIC_IP_HAS_PORT_FORWARDS | 409 | Delete its rules first |
PUBLIC_IP_HAS_LOAD_BALANCERS | 409 | Delete the address's load balancers first |
INSTANCE_BEHIND_LOAD_BALANCER | 409 | Remove the instance from its load balancers' targets first |
PORTS_IN_USE | 409 | Another rule forwards these ports |
SNAPSHOT_NAME_TAKEN | 409 | Choose another name |
SECURITY_GROUP_NAME_TAKEN | 409 | Choose another name |
SSH_KEY_NAME_TAKEN | 409 | Choose another name |
SSH_KEY_ALREADY_EXISTS | 409 | The key is already registered |
SNAPSHOT_SCHEDULE_NOT_FOUND | 404 | No schedule is set; PUT one |
PROVISIONING_UNAVAILABLE | 503 | Retry with backoff |
CONSOLE_UNAVAILABLE | 503 | Retry shortly |
OPERATION_STREAM_UNAVAILABLE, OPERATION_STREAM_TIMEOUT | event | The operation's event stream ended; reconnect, or poll the operation |
SECURITY_GROUP_ATTACHED_TO_DATABASE | 409 | Detach the group from the database named in detail, then delete it |
INSTANCE_NOT_SETTLED | 409 | Wait until the instance is running or stopped with nothing else in progress |
Database codes
The database endpoints have codes of their own, with a page that explains each one: database error codes. In short:
code | Status | What to do |
|---|---|---|
DATABASE_NOT_FOUND, DATABASE_ORDER_NOT_FOUND, DATABASE_SNAPSHOT_NOT_FOUND | 404 | Check the ids; a snapshot is found only under its own database |
DATABASE_NAME_TAKEN, DATABASE_SNAPSHOT_NAME_TAKEN | 409 | Choose another name |
DATABASE_NOT_RUNNING | 409 | Start the database first (password changes and storage resizes need it running) |
DATABASE_NOT_STOPPED | 409 | Stop the database first |
DATABASE_SNAPSHOT_NOT_READY | 409 | Wait until the database is running or stopped |
DATABASE_STORAGE_RESIZE_IN_PROGRESS | 409 | Wait for the resize in flight to finish |
DATABASE_ACCESS_RANGE_LIMIT_EXCEEDED | 422 | Keep rules plus security-group ranges to 1,024 |
DATABASE_HOSTNAME_UNAVAILABLE | 503 | Retry with a new Idempotency-Key |
DATABASE_STORAGE_UNAVAILABLE | 503 | Storage is not offered in that zone yet; try later |
DATABASES_UNAVAILABLE | 503 | Databases are unavailable right now; try later |
Kubernetes codes
The managed Kubernetes endpoints have codes of their own; the guide's troubleshooting explains each, field codes included. In short:
code | Status | What to do |
|---|---|---|
KUBERNETES_CLUSTER_NOT_FOUND | 404 | Check the cluster id (k8s_…). A deleted cluster answers this at once, although its nodes are destroyed 12 hours later |
KUBERNETES_CLUSTER_NAME_TAKEN | 409 | Choose another name |
KUBERNETES_CLUSTER_LIMIT | 409 | Delete a cluster, or contact support to raise the limit |
KUBERNETES_CLUSTER_NOT_RUNNING | 409 | Start the cluster first; scaling, autoscaling, API server access and upgrades need it running |
KUBERNETES_CLUSTER_BUSY | 409 | Wait until the cluster is running or stopped |
KUBERNETES_CLUSTER_NOT_CHANGEABLE | 409 | It is stopped, being deleted, or failed |
KUBECONFIG_UNAVAILABLE | 409 | Wait until the cluster is running |
KUBERNETES_UNAVAILABLE | 503 | Managed Kubernetes is not open on the platform yet |
Private network codes
From adding a private database network interface to a standard instance:
code | Status | What to do |
|---|---|---|
SECURITY_GROUP_ALLOWS_PRIVATE_NETWORK | 409 | Narrow the rules named in detail so none admits 10.250.0.0/20, ICMP included |
PRIVATE_NETWORK_NOT_AVAILABLE | 409 | Only standard instances in af-abj-1 can have one |
PRIVATE_NETWORK_ALREADY_ATTACHED | 409 | It already has one; read private_network_ip |
PRIVATE_NETWORK_NOT_ATTACHED | 409 | It has none to remove |
PRIVATE_NETWORK_CHANGE_IN_PROGRESS | 409 | Follow the attach or detach already running |
Field codes
These appear inside errors on a 422, each with the field it is about.
code | Typical field | Means |
|---|---|---|
REQUIRED | any | The field is missing |
UNKNOWN_FIELD | any | Not a field of this request |
PLAN_NOT_FOUND | plan_slug | Not an active plan |
PLAN_NOT_BIGGER | plan_slug | Resize needs a bigger plan |
IMAGE_NOT_FOUND | image_slug | Not an active image |
IMAGE_NOT_AVAILABLE_IN_ZONE | image_slug | Not offered where this instance runs |
ENCRYPTION_NOT_SUPPORTED | encrypted | encrypted is true, but this image has no encrypted version available to your organization |
ENCRYPTION_CONFLICT | encrypted | encrypted is false, but image_slug is already an encrypted image |
SSH_KEY_NOT_FOUND | ssh_key_id | Not a key on this account |
REGION_NOT_AVAILABLE | region | Not available for this resource |
SUBNET_REQUIRED | subnet_id | Needed when network_id is set |
SUBNET_NOT_FOUND | subnet_id | Not a subnet on this account |
SUBNET_NOT_IN_NETWORK | subnet_id | Not in network_id |
SUBNET_NOT_USABLE | subnet_id | Being deleted or failed |
SECURITY_GROUP_NOT_FOUND | security_group_id | Not a group on this account |
CONFIRM_NAME_MISMATCH | confirm_name | Not the instance's current name |
DISK_OFFERING_NOT_FOUND | disk_offering_slug | Not an active offering |
SIZE_REQUIRED | size_gb | Needed for a custom-size offering |
INVALID_SIZE | size_gb | Must be a whole number of GB |
INSTANCE_NOT_FOUND | instance_id | Not an instance on this account |
INSTANCE_NOT_RUNNING | instance_id | Must be running |
INSTANCE_WRONG_ZONE | instance_id | Must be in the volume's zone |
INSTANCE_NOT_IN_NETWORK | instance_id | Must be in the address's VPC |
NOT_ALLOWED | instance_id | Not taken here |
INVALID_MOUNT_POINT | mount_point | Must be an absolute path |
INVALID_RULE_NUMBER | number | Must be 100–9999 |
RULE_NUMBER_TAKEN | number | Used by another rule |
INVALID_PORTS | port_start | Ports invalid for the protocol |
INVALID_FIREWALL_RULE | protocol | Direction, protocol or action invalid |
PURPOSE_NOT_AVAILABLE | purpose | Not available yet |
INVALID_SSH_PUBLIC_KEY | public_key | Not an OpenSSH public key |
INVALID_NAME, NAME_TOO_LONG | name | 1–255 characters |
INVALID_METRIC_INTERVAL | interval | One of 1h, 24h, 7d, 30d |
INVALID_PERIOD | period | One of 30d, 7d, month, last_month |
INVALID_PLACEMENT | placement | standard or vpc |
UNSUPPORTED_CURRENCY | currency | NGN or USD |
INVALID_MACHINE | machines | A line in the estimate is invalid |
TOO_MANY_MACHINES | machines | At most 50 lines |
INVALID_DATABASE_NAME | name | 1–255 characters |
INVALID_DATABASE_ENGINE | engine | postgresql, mysql or mariadb |
INVALID_DATABASE_VERSION | version | Not an offered version line |
INVALID_DATABASE_ZONE | zone_id | Not a zone you can use |
DATABASE_VERSION_NOT_AVAILABLE_IN_ZONE | zone_id | That version is not offered in that zone |
DATABASE_SUBNET_REQUIRED, DATABASE_SUBNET_NOT_ALLOWED | subnet_id | Required in af-abj-2, refused in af-abj-1 |
DATABASE_SUBNET_NOT_FOUND, DATABASE_SUBNET_WRONG_ZONE, DATABASE_SUBNET_NOT_USABLE | subnet_id | Not a usable subnet of yours in zone_id |
INVALID_DATABASE_ADMIN_USERNAME | admin_username | 3–32 lowercase letters, digits, underscores; not reserved |
INVALID_DATABASE_PASSWORD | password | 16–128 printable characters, no spaces, quotes or backslashes |
INVALID_DATABASE_STORAGE | storage_gb | Plan's size to 2,000 GB, in 10 GB steps; a resize must grow |
INVALID_DATABASE_SNAPSHOT_NAME | name | 1–255 characters |
INVALID_ACCESS_RULE | access_rules | An IPv4 CIDR no wider than /8, never 0.0.0.0/0 |
TOO_MANY_ACCESS_RULES | access_rules | At most 50 |
DUPLICATE_ACCESS_RULE | access_rules | Each CIDR once |
INVALID_DATABASE_SECURITY_GROUPS | security_group_ids | At most 5, each once |
DATABASE_SECURITY_GROUP_NOT_FOUND, DATABASE_SECURITY_GROUP_WRONG_ZONE, DATABASE_SECURITY_GROUP_NOT_USABLE | security_group_ids | Your own usable groups in the database's zone |
RULE_ALLOWS_PRIVATE_NETWORK | rules | An instance on this group has a private network interface; leave 10.250.0.0/20 out |
SECURITY_GROUP_ALLOWS_PRIVATE_NETWORK | security_group_id | The instance has a private network interface; pick a group that admits nothing from 10.250.0.0/20 |
DATABASE_ACCESS_RANGE_LIMIT_EXCEEDED | rules | An attached database would get more than 1,024 ranges |
INVALID_KUBERNETES_WORKER_COUNT | workers | 1 to 10 |
INVALID_KUBERNETES_AUTOSCALING | autoscaling | min_workers and max_workers 1 to 10, minimum no more than maximum, workers within them; no workers while autoscaling is on |
INVALID_API_ALLOWED_CIDRS | api_allowed_cidrs | At most 20 IPv4 ranges, like 203.0.113.0/24 |
API_ALLOWED_CIDRS_VPC_ONLY | api_allowed_cidrs | Only clusters in a VPC zone can limit API server access |
When an operation fails
An accepted change (202) can still fail later. Its operation then ends failed, and its failure says why:
{
"id": "op_…",
"kind": "attach_private_network",
"status": "failed",
"failure": {
"code": "SECURITY_GROUP_ALLOWS_PRIVATE_NETWORK",
"reason": "The instance's security group was changed to allow 10.250.0.0/20 before the interface was added. Narrow it, then try again."
}
}Branch on failure.code, such as DATABASE_STORAGE_GROW_FAILED or INSTANCE_PRIVATE_NETWORK_ATTACH_FAILED; show failure.reason, whose wording can change. The resource is left as it was before. These are listed with the guide for each feature, for example the database error codes. Retry a failed operation with a new Idempotency-Key: the old one replays the failure.
A change the platform itself could not carry out (creating, starting or resizing a machine, for example) fails with one of these codes, whatever the resource:
failure.code | Means | What to do |
|---|---|---|
PROVISIONING_CAPACITY_UNAVAILABLE | No capacity for it in the zone right now | Try a different plan or zone, or try again later |
PROVISIONING_LIMIT_EXCEEDED | Your account reached its limit for this kind of resource. A Kubernetes cluster or scale counts every node it could have, up to its autoscaling maximum, and fails before anything is created | Delete what you no longer use, ask for less, or contact support to raise the limit |
PROVISIONING_ADDRESSES_EXHAUSTED | No free addresses in the zone right now | Try again later, or choose another zone |
PROVISIONING_IMAGE_UNAVAILABLE | The image is not available in this zone right now | Choose another image or zone, or try again later |
PROVISIONING_RESOURCE_BUSY | The resource was busy with another change | Wait for it to settle, then retry |
PROVISIONING_RESOURCE_CONFLICT | A resource with these settings already exists | Use a different name or settings |
PROVISIONING_RESOURCE_NOT_FOUND | The resource no longer exists on the platform | Refresh, then retry |
PROVISIONING_INVALID_REQUEST | A setting is not valid here | Check the request, then retry |
PROVISIONING_UNAVAILABLE | The platform is temporarily unavailable | Retry in a few minutes |
PROVISIONING_INTERNAL_ERROR | Something failed on our side; we have been alerted | Retrying will not help; contact support with the operation id |
PROVISIONING_FAILED | The change could not be completed | Retry; contact support with the operation id if it keeps failing |
New codes may be added within v1. Treat an unknown code by its status.