Developer Hub API Reference

DartNode API

Programmatic access to your entire DartNode infrastructure. Deploy servers, manage networking, control power states, and automate your workflow.

Base URL

Base URL
https://api.dartnode.net/v1

All requests and responses use JSON. Include Content-Type: application/json in request bodies and Accept: application/json for all requests.

Quick Start

Get up and running in three steps. This guide walks you through creating an API token and making your first request.

1

Create an API Token

Go to Settings → API Keys in your dashboard. Click Create Token, give it a name, and select the scopes you need. Copy the token - it won't be shown again.

2

Make Your First Request

Use the token to fetch your account details:

cURL
curl https://api.dartnode.net/v1/account \
  -H "Authorization: Bearer dnt_your_token_here" \
  -H "Accept: application/json"
Python
import requests

API_TOKEN = "dnt_your_token_here"
BASE_URL = "https://api.dartnode.net/v1"

resp = requests.get(f"{BASE_URL}/account",
    headers={"Authorization": f"Bearer {API_TOKEN}"})

data = resp.json()
print(f"Hello, {data['data']['first_name']}!")
Node.js
const API_TOKEN = "dnt_your_token_here";
const BASE_URL = "https://api.dartnode.net/v1";

const resp = await fetch(`${BASE_URL}/account`, {
    headers: { "Authorization": `Bearer ${API_TOKEN}` }
});

const { data } = await resp.json();
console.log(`Hello, ${data.first_name}!`);
PHP
$apiToken = "dnt_your_token_here";
$baseUrl = "https://api.dartnode.net/v1";

$ch = curl_init("$baseUrl/account");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiToken"],
    CURLOPT_RETURNTRANSFER => true,
]);

$data = json_decode(curl_exec($ch), true);
echo "Hello, {$data['data']['first_name']}!";
3

Explore the API

You're in. Now you can list your instances, control power states, deploy new servers, or set up webhooks for real-time notifications.

Authentication

All API requests require authentication via a Bearer token in the Authorization header. The API supports two token types.

Service Keys (Personal Tokens)

Scoped tokens tied directly to your account. Best for scripts, automation, and server-to-server integrations. Create them from the dashboard.

HTTP Header
Authorization: Bearer dnt_your_token_here

OAuth 2.0 Access Tokens

For third-party apps acting on behalf of users. Obtained through the OAuth 2.0 authorization code flow.

HTTP Header
Authorization: Bearer dno_access_token_here

Token Prefixes

All tokens use a prefix to identify their type. This helps with debugging and log scanning.

PrefixTypeUsage
dnt_Service KeyPersonal API token for direct access
dna_OAuth client_idIdentifies your OAuth application
dns_OAuth client_secretServer-side secret for token exchange
dno_OAuth access tokenShort-lived token for API access (1 hour)
dnr_OAuth refresh tokenLong-lived token to renew access (30 days)

Scopes

Tokens are scoped to limit access. When creating a service key or OAuth app, select only the scopes your integration needs.

ScopeDescription
account.readView account profile and settings
services.readList and view services and their details
services.powerStart, stop, restart, and reset VMs
services.consoleCreate VNC console sessions
services.config.readView service configuration and OS details
services.config.writeModify hostname, reinstall OS, reset password
network.readList IP addresses and view rDNS records
network.rdns.writeCreate, update, and clear reverse DNS entries
billing.readView invoices, transactions, and account balance
deployDeploy new services using account credit
deploy.readList available plans, regions, and OS templates
webhooks.readList and view webhook endpoints
webhooks.writeCreate, update, and delete webhooks

Requests & Pagination

Standard request conventions and how to work with paginated results.

Request Format

Send JSON request bodies with Content-Type: application/json. All responses return JSON with a top-level success boolean:

Successful Response
{
    "success": true,
    "data": { ... }
}

Pagination

List endpoints return paginated results. Control pagination with these query parameters:

ParameterTypeDefaultDescription
pageinteger1Page number to retrieve
per_pageinteger25Results per page (max 100)

Paginated responses include metadata to help you navigate:

Paginated Response
{
    "success": true,
    "data": {
        "services": [ ... ],
        "pagination": {
            "page": 1,
            "per_page": 25,
            "total": 48,
            "total_pages": 2
        }
    }
}
Tip: For large collections, iterate through pages until page equals total_pages or the result set is empty.

Rate Limiting

API requests are rate-limited per token using a sliding window. Check the response headers to monitor your usage.

X-RateLimit-LimitMaximum requests per window
X-RateLimit-RemainingRemaining requests in current window
X-RateLimit-ResetUnix timestamp when the window resets

Limits by Endpoint Type

CategoryLimitExamples
Read operations120 req/minGET endpoints (list, detail)
Power control30 req/minStart, stop, restart, reset
Console sessions10 req/minVNC session creation
Write operations5 req/minDeploy, reinstall, config changes

When rate limited, the API returns 429 Too Many Requests. Use the X-RateLimit-Reset header to know when to retry.

429 Response
{
    "success": false,
    "message": "Rate limit exceeded. Try again in 45 seconds."
}

Error Handling

Errors return an appropriate HTTP status code with a JSON body describing what went wrong.

Error Response
{
    "success": false,
    "message": "Description of what went wrong"
}

Status Codes

CodeMeaningWhat to Do
400Bad RequestCheck your request body and parameters
401UnauthorizedToken is missing, invalid, or expired
402Payment RequiredInsufficient account credit for the action
403ForbiddenToken lacks the required scope for this endpoint
404Not FoundResource doesn't exist or isn't owned by your account
409ConflictResource state prevents the action (e.g., VM already running)
429Too Many RequestsRate limited - wait and retry
500Internal Server ErrorSomething went wrong on our end - try again or contact support
Best practice: Always check the success field in the response body, not just the HTTP status code. Some 200 responses may still contain partial errors.

Account

Retrieve your account profile, user details, and current balance.

GET /v1/account account.read Get account profile
cURL
curl https://api.dartnode.net/v1/account \
  -H "Authorization: Bearer dnt_your_token"
Python
resp = requests.get(f"{BASE_URL}/account",
    headers={"Authorization": f"Bearer {API_TOKEN}"})

account = resp.json()["data"]
print(f"{account['first_name']} {account['last_name']} ({account['email']})")
Node.js
const resp = await fetch(`${BASE_URL}/account`, {
    headers: { "Authorization": `Bearer ${API_TOKEN}` }
});

const { data } = await resp.json();
console.log(`${data.first_name} ${data.last_name} (${data.email})`);
PHP
$ch = curl_init("$baseUrl/account");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiToken"],
    CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true)["data"];
echo "{$data['first_name']} {$data['last_name']} ({$data['email']})";

Response

JSON
{
    "success": true,
    "data": {
        "id": 1,
        "email": "[email protected]",
        "first_name": "John",
        "last_name": "Doe",
        "profile_name": "JohnDoe",
        "is_verified": true,
        "two_factor_enabled": false,
        "created_at": "2024-01-15T10:30:00+00:00",
        "account": {
            "id": 1,
            "name": "John Doe",
            "country": "US",
            "state": "Texas",
            "city": "Dallas"
        },
        "urls": {
            "compute": "https://api.dartnode.net/v1/compute",
            "billing_balance": "https://api.dartnode.net/v1/billing/balance",
            "invoices": "https://api.dartnode.net/v1/billing/invoices",
            "network_ips": "https://api.dartnode.net/v1/network/ips",
            "webhooks": "https://api.dartnode.net/v1/webhooks",
            "deploy": "https://api.dartnode.net/v1/deploy"
        }
    }
}
Note: The response includes a urls object with HATEOAS-style links to related resources, making it easy to discover available API endpoints.

Cloud Compute

Manage your cloud compute instances (virtual machines). List, inspect, control power states, access consoles, and modify configuration.

List Instances

GET /v1/compute services.read List all compute instances

Returns a paginated list of all virtual machines on your account. Each instance includes its current power state, IP addresses, resource specs, and real-time usage metrics.

Query Parameters

ParameterTypeDescription
pageinteger optionalPage number (default: 1)
per_pageinteger optionalResults per page, max 100 (default: 25)
statusstring optionalFilter by status: active, suspended, pending, terminated
cURL
curl "https://api.dartnode.net/v1/compute?status=active&per_page=10" \
  -H "Authorization: Bearer dnt_your_token"
Python
resp = requests.get(f"{BASE_URL}/compute",
    headers={"Authorization": f"Bearer {API_TOKEN}"},
    params={"status": "active", "per_page": 10})

for vm in resp.json()["data"]["services"]:
    print(f"{vm['id']}: {vm['hostname']} ({vm['status']})")
Node.js
const resp = await fetch(`${BASE_URL}/compute?status=active&per_page=10`, {
    headers: { "Authorization": `Bearer ${API_TOKEN}` }
});

const { data } = await resp.json();
data.services.forEach(vm =>
    console.log(`${vm.id}: ${vm.hostname} (${vm.status})`));
PHP
$ch = curl_init("$baseUrl/compute?status=active&per_page=10");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiToken"],
    CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true)["data"];
foreach ($data["services"] as $vm) {
    echo "{$vm['id']}: {$vm['hostname']} ({$vm['status']})\n";
}

Response

JSON
{
    "success": true,
    "data": {
        "services": [
            {
                "id": 123,
                "url": "https://api.dartnode.net/v1/compute/123",
                "hostname": "web-server-1",
                "status": "running",
                "service_status": "active",
                "primary_ip": "192.0.2.10",
                "ips": [
                    {
                        "address": "192.0.2.10",
                        "type": "ipv4",
                        "ptr": "web.example.com",
                        "is_primary": true,
                        "has_ddos_protection": false,
                        "url": "https://api.dartnode.net/v1/network/ips/192.0.2.10",
                        "rdns_url": "https://api.dartnode.net/v1/network/ips/192.0.2.10/rdns"
                    }
                ],
                "cpu": {
                    "cores": 2,
                    "usage_percent": 12.5
                },
                "memory": {
                    "total_mb": 4096,
                    "used_mb": 1024,
                    "usage_percent": 25.0
                },
                "disk_gb": 80,
                "region": "us-dal",
                "created_at": "2024-06-10T14:00:00+00:00"
            }
        ],
        "pagination": {
            "page": 1,
            "per_page": 10,
            "total": 3,
            "total_pages": 1
        }
    }
}

Response Fields

FieldTypeDescription
idintegerUnique instance identifier
urlstringAPI URL to fetch full instance details
hostnamestringInstance hostname
statusstringVM power state: running, stopped, provisioning, unknown
service_statusstringBilling lifecycle: active, suspended, terminated
primary_ipstringPrimary IPv4 address
ipsarrayAll assigned IP addresses with rDNS and DDoS info
cpuobjectCPU core count and real-time usage percentage
memoryobjectTotal, used, and percentage memory utilization
disk_gbintegerDisk allocation in GB
regionstringCompute region code

Get Instance

GET /v1/compute/{sid} services.read Get instance details

Returns full details for a single compute instance including current resource usage, IP addresses, OS information, billing, and HATEOAS links to related actions.

Path Parameters

ParameterTypeDescription
sidinteger requiredInstance (service) ID
cURL
curl https://api.dartnode.net/v1/compute/123 \
  -H "Authorization: Bearer dnt_your_token"
Python
resp = requests.get(f"{BASE_URL}/compute/123",
    headers={"Authorization": f"Bearer {API_TOKEN}"})

vm = resp.json()["data"]
print(f"{vm['hostname']} - {vm['os']} ({vm['status']})")
print(f"CPU: {vm['cpu']['cores']} cores @ {vm['cpu']['usage_percent']}%")
print(f"RAM: {vm['memory']['used_mb']}MB / {vm['memory']['total_mb']}MB")
Node.js
const resp = await fetch(`${BASE_URL}/compute/123`, {
    headers: { "Authorization": `Bearer ${API_TOKEN}` }
});

const { data: vm } = await resp.json();
console.log(`${vm.hostname} - ${vm.os} (${vm.status})`);
console.log(`CPU: ${vm.cpu.cores} cores @ ${vm.cpu.usage_percent}%`);
console.log(`RAM: ${vm.memory.used_mb}MB / ${vm.memory.total_mb}MB`);
PHP
$ch = curl_init("$baseUrl/compute/123");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer $apiToken"],
    CURLOPT_RETURNTRANSFER => true,
]);
$vm = json_decode(curl_exec($ch), true)["data"];
echo "{$vm['hostname']} - {$vm['os']} ({$vm['status']})";

Response

JSON
{
    "success": true,
    "data": {
        "id": 123,
        "url": "https://api.dartnode.net/v1/compute/123",
        "hostname": "web-server-1",
        "status": "running",
        "service_status": "active",
        "ips": [
            {
                "address": "192.0.2.10",
                "type": "ipv4",
                "ptr": "web.example.com",
                "is_primary": true,
                "has_ddos_protection": false,
                "url": "https://api.dartnode.net/v1/network/ips/192.0.2.10",
                "rdns_url": "https://api.dartnode.net/v1/network/ips/192.0.2.10/rdns"
            }
        ],
        "cpu": {
            "cores": 2,
            "usage_percent": 12.5
        },
        "memory": {
            "total_mb": 4096,
            "used_mb": 1024,
            "usage_percent": 25.0
        },
        "disk_gb": 80,
        "os": "Ubuntu 24.04",
        "region": "us-dal",
        "billing": {
            "amount": 9.95,
            "term_months": 1,
            "next_payment": "2026-04-10"
        },
        "created_at": "2024-06-10T14:00:00+00:00",
        "urls": {
            "power": "https://api.dartnode.net/v1/compute/123/power",
            "console_vnc": "https://api.dartnode.net/v1/compute/123/console/vnc",
            "config": "https://api.dartnode.net/v1/compute/123/config"
        }
    }
}
Status fields: The status field reflects the VM power state (running, stopped, provisioning, unknown), while service_status reflects the billing lifecycle (active, suspended, terminated). A VM can be stopped but still active in billing.

Power Control

Start, stop, gracefully shutdown, or hard reset your compute instances.

POST /v1/compute/{sid}/power services.power Control instance power state

Request Body

ParameterTypeDescription
actionstring requiredPower action to perform

Available Actions

ActionDescription
onPower on the instance (boot from off state)
offImmediate power off (like pulling the plug - data loss possible)
shutdownGraceful ACPI shutdown signal (recommended)
resetHard reset (power cycle without graceful shutdown)
Use shutdown instead of off whenever possible. The off action is equivalent to pulling the power cable and may cause filesystem corruption on the guest OS.
cURL
curl -X POST https://api.dartnode.net/v1/compute/123/power \
  -H "Authorization: Bearer dnt_your_token" \
  -H "Content-Type: application/json" \
  -d '{"action": "shutdown"}'
Python
resp = requests.post(f"{BASE_URL}/compute/123/power",
    headers={"Authorization": f"Bearer {API_TOKEN}"},
    json={"action": "shutdown"})

print(resp.json()["data"]["message"])
Node.js
const resp = await fetch(`${BASE_URL}/compute/123/power`, {
    method: "POST",
    headers: {
        "Authorization": `Bearer ${API_TOKEN}`,
        "Content-Type": "application/json"
    },
    body: JSON.stringify({ action: "shutdown" })
});

console.log((await resp.json()).data.message);
PHP
$ch = curl_init("$baseUrl/compute/123/power");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer $apiToken",
        "Content-Type: application/json"
    ],
    CURLOPT_POSTFIELDS => json_encode(["action" => "shutdown"]),
    CURLOPT_RETURNTRANSFER => true,
]);
echo json_decode(curl_exec($ch), true)["data"]["message"];

Response

JSON
{
    "success": true,
    "data": {
        "action": "shutdown",
        "message": "Shutdown signal sent"
    }
}

Console Access

Get direct graphical console access to your compute instances via a WebSocket-based VNC session. Useful for recovery, debugging, or when SSH is unreachable.

POST /v1/compute/{sid}/console/vnc services.console Create VNC console session

Returns a WebSocket URL with a time-limited token. Connect to the URL promptly to open the session.

Response

JSON
{
    "success": true,
    "data": {
        "url": "wss://node1.dartnode.net:8443/vnc/vm-123?token=abc123",
        "token": "abc123"
    }
}
Note: VNC provides a full graphical console (like a monitor attached to the VM) and works even when the OS isn't booted or SSH is unreachable.

Configuration

View and modify instance configuration. Change hostnames, reinstall the operating system, and reset root passwords.

GET /v1/compute/{sid}/config services.config.read View instance configuration

Config Response

JSON
{
    "success": true,
    "data": {
        "hostname": "web-server-1",
        "os": "Ubuntu 24.04",
        "os_type": "linux",
        "cpu": 2,
        "memory_mb": 4096,
        "disk_gb": 80,
        "has_root_password": true
    }
}
PATCH /v1/compute/{sid}/hostname services.config.write Update hostname
ParameterTypeDescription
hostnamestring requiredNew hostname (1-255 chars, alphanumeric with dots/dashes)
POST /v1/compute/{sid}/reinstall services.config.write Reinstall OS
Destructive action. Reinstalling the OS will wipe all data on the instance. This action cannot be undone. Create a backup first if you need to preserve data.
ParameterTypeDescription
osinteger requiredOS template ID (from /v1/deploy/os-templates)

Reinstall Response

JSON
{
    "success": true,
    "data": {
        "job_id": "x1y2z3",
        "message": "OS reinstall queued"
    }
}
POST /v1/compute/{sid}/reset-password services.config.write Reset root password

Queues a root password reset job. The new password will be delivered via email once the operation completes.

Response

JSON
{
    "success": true,
    "data": {
        "job_id": "a1b2c3d4",
        "message": "Root password reset queued"
    }
}
Note: The password reset is processed asynchronously. Poll the instance via GET /v1/compute/{sid} to check when the operation completes.

Networking

Manage IP addresses assigned to your services and configure reverse DNS (rDNS) records for email deliverability and service identification.

GET /v1/network/ips network.read List all IP addresses

Response

JSON
{
    "success": true,
    "data": {
        "ips": [
            {
                "address": "192.0.2.10",
                "type": "ipv4",
                "ptr": "web.example.com",
                "resolved_ptr": "web.example.com",
                "has_ddos_protection": false,
                "attached_to": {
                    "id": 123,
                    "hostname": "web-server-1"
                },
                "url": "https://api.dartnode.net/v1/network/ips/192.0.2.10",
                "rdns_url": "https://api.dartnode.net/v1/network/ips/192.0.2.10/rdns"
            }
        ],
        "total": 1
    }
}
GET /v1/network/ips/{ip} network.read Get IP detail + rDNS
PUT /v1/network/ips/{ip}/rdns network.rdns.write Set reverse DNS
ParameterTypeDescription
valuestring requiredFully qualified domain name (e.g. mail.example.com)
cURL
curl -X PUT https://api.dartnode.net/v1/network/ips/192.0.2.1/rdns \
  -H "Authorization: Bearer dnt_your_token" \
  -H "Content-Type: application/json" \
  -d '{"value": "mail.example.com"}'
Python
resp = requests.put(f"{BASE_URL}/network/ips/192.0.2.1/rdns",
    headers={"Authorization": f"Bearer {API_TOKEN}"},
    json={"value": "mail.example.com"})

print(resp.json()["data"]["message"])
Node.js
await fetch(`${BASE_URL}/network/ips/192.0.2.1/rdns`, {
    method: "PUT",
    headers: {
        "Authorization": `Bearer ${API_TOKEN}`,
        "Content-Type": "application/json"
    },
    body: JSON.stringify({ value: "mail.example.com" })
});
PHP
$ch = curl_init("$baseUrl/network/ips/192.0.2.1/rdns");
curl_setopt_array($ch, [
    CURLOPT_CUSTOMREQUEST => "PUT",
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer $apiToken",
        "Content-Type: application/json"
    ],
    CURLOPT_POSTFIELDS => json_encode(["value" => "mail.example.com"]),
    CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);
DELETE /v1/network/ips/{ip}/rdns network.rdns.write Clear reverse DNS

Removes the rDNS record for the specified IP address, reverting it to the default ISP hostname.

Billing

Access your invoices, view transaction history, and check your account balance.

GET /v1/billing/invoices billing.read List invoices

Query Parameters

ParameterTypeDescription
pageinteger optionalPage number (default: 1)
per_pageinteger optionalResults per page (default: 25)
statusstring optionalFilter: paid, unpaid, overdue, refunded

Response

JSON
{
    "success": true,
    "data": {
        "invoices": [
            {
                "id": 789,
                "url": "https://api.dartnode.net/v1/billing/invoices/789",
                "description": "Cloud VPS 4GB - Monthly",
                "status": "paid",
                "total": 9.95,
                "credit_applied": 0.00,
                "due_at": "2026-03-01T00:00:00+00:00",
                "paid_at": "2026-02-28T12:00:00+00:00",
                "created_at": "2026-02-15T10:00:00+00:00"
            }
        ],
        "pagination": { "page": 1, "per_page": 25, "total": 12, "total_pages": 1 }
    }
}
GET /v1/billing/invoices/{id} billing.read Get invoice detail

Invoice Detail Response

JSON
{
    "success": true,
    "data": {
        "id": 789,
        "url": "https://api.dartnode.net/v1/billing/invoices/789",
        "description": "Cloud VPS 4GB - Monthly",
        "status": "paid",
        "total": 9.95,
        "credit_applied": 0.00,
        "due_at": "2026-03-01T00:00:00+00:00",
        "paid_at": "2026-02-28T12:00:00+00:00",
        "created_at": "2026-02-15T10:00:00+00:00",
        "items": [
            {
                "id": 1,
                "description": "Cloud VPS 4GB - Monthly",
                "amount": 9.95,
                "quantity": 1,
                "total": 9.95
            }
        ]
    }
}
GET /v1/billing/balance billing.read Get account balance

Balance Response

JSON
{
    "success": true,
    "data": {
        "balance": -25.50,
        "currency": "USD"
    }
}
Note: The balance field is negative when you have credit. A balance of -25.50 means you have $25.50 of credit available.

Deploy

Provision new virtual machines using your account credit. The deploy flow lets you choose a plan, region, and OS, then launch a server in seconds.

Step 1: Browse Available Options

GET /v1/deploy/plans deploy.read List available plans
GET /v1/deploy/regions deploy.read List compute regions
GET /v1/deploy/os-templates deploy.read List OS templates

Step 2: Estimate Cost

GET /v1/deploy/estimate deploy.read Get price estimate

Pass the same parameters as the deploy request to get a price breakdown before committing.

Step 3: Deploy

POST /v1/deploy deploy Deploy a new VM

Request Body

ParameterTypeDescription
plan_idinteger requiredPlan ID from /v1/deploy/plans
regionstring requiredRegion code from /v1/deploy/regions
hostnamestring requiredServer hostname
osinteger requiredOS category ID
os_versioninteger requiredOS template ID
ipv4integer optionalNumber of IPv4 addresses (default: 1)
ipv6integer optionalNumber of IPv6 addresses (default: 1)
ddosboolean optionalEnable DDoS protection ($2.95/mo)
backupsboolean optionalEnable cloud backups ($4.95/mo)
cycleinteger optionalBilling cycle in months: 1, 3, 6, or 12 (default: 1)
cURL
curl -X POST https://api.dartnode.net/v1/deploy \
  -H "Authorization: Bearer dnt_your_token" \
  -H "Content-Type: application/json" \
  -d '{
    "plan_id": 1,
    "region": "us-dal",
    "hostname": "my-server",
    "os": 1,
    "os_version": 5,
    "ddos": false,
    "backups": true,
    "cycle": 1
}'
Python
resp = requests.post(f"{BASE_URL}/deploy",
    headers={"Authorization": f"Bearer {API_TOKEN}"},
    json={
        "plan_id": 1,
        "region": "us-dal",
        "hostname": "my-server",
        "os": 1,
        "os_version": 5,
        "backups": True,
        "cycle": 1
    })

deploy = resp.json()["data"]["deploy"]
print(f"Service #{deploy['service_id']} - {deploy['status']}")
Node.js
const resp = await fetch(`${BASE_URL}/deploy`, {
    method: "POST",
    headers: {
        "Authorization": `Bearer ${API_TOKEN}`,
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        plan_id: 1,
        region: "us-dal",
        hostname: "my-server",
        os: 1,
        os_version: 5,
        backups: true,
        cycle: 1
    })
});

const { data } = await resp.json();
console.log(`Service #${data.deploy.service_id} - ${data.deploy.status}`);
PHP
$ch = curl_init("$baseUrl/deploy");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer $apiToken",
        "Content-Type: application/json"
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "plan_id" => 1,
        "region" => "us-dal",
        "hostname" => "my-server",
        "os" => 1,
        "os_version" => 5,
        "backups" => true,
        "cycle" => 1
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true)["data"];
echo "Service #{$data['deploy']['service_id']}";

Deploy Response

JSON
{
    "success": true,
    "data": {
        "message": "VM deployment initiated successfully. The server will be provisioned shortly.",
        "deploy": {
            "order_id": "abc123",
            "service_id": 456,
            "service_url": "https://api.dartnode.net/v1/compute/456",
            "invoice_id": 790,
            "invoice_url": "https://api.dartnode.net/v1/billing/invoices/790",
            "status": "provisioning",
            "amount_charged": 9.95,
            "recurring_amount": 9.95,
            "billing_cycle_months": 1,
            "next_due_date": "2026-04-05",
            "hostname": "my-server",
            "plan": {
                "id": 1,
                "name": "Cloud VPS 2GB",
                "vcpu": 1,
                "memory_mb": 2048,
                "storage_gb": 40
            },
            "region": "us-dal",
            "os": {
                "category": "Ubuntu",
                "template": "Ubuntu 24.04"
            }
        }
    }
}
Provisioning time: Most servers are ready within 2-5 minutes. Use GET /v1/compute/{sid} to poll the status until it changes from provisioning to active. You can also set up a webhook to get notified when the server is ready.

Webhooks API

Create and manage webhook endpoints to receive real-time HTTP notifications when events occur on your account.

GET /v1/webhooks webhooks.read List webhooks
GET /v1/webhooks/{id} webhooks.read Get webhook detail + recent deliveries
POST /v1/webhooks webhooks.write Create webhook
PATCH /v1/webhooks/{id} webhooks.write Update webhook
DELETE /v1/webhooks/{id} webhooks.write Delete webhook
GET /v1/webhooks/events webhooks.read List available event types

Create Webhook

ParameterTypeDescription
namestring requiredDisplay name for the webhook
urlstring requiredHTTPS endpoint URL that will receive events
eventsarray requiredEvent types to subscribe to, or ["*"] for all events
cURL
curl -X POST https://api.dartnode.net/v1/webhooks \
  -H "Authorization: Bearer dnt_your_token" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production Alerts",
    "url": "https://yourapp.com/webhooks/dartnode",
    "events": ["service.provisioned", "service.suspended", "invoice.paid"]
}'
Python
resp = requests.post(f"{BASE_URL}/webhooks",
    headers={"Authorization": f"Bearer {API_TOKEN}"},
    json={
        "name": "Production Alerts",
        "url": "https://yourapp.com/webhooks/dartnode",
        "events": ["service.provisioned", "service.suspended", "invoice.paid"]
    })

webhook = resp.json()["data"]
print(f"Webhook #{webhook['webhook']['id']} created with secret: {webhook['webhook']['secret']}")
                
Node.js
const resp = await fetch(`${BASE_URL}/webhooks`, {
    method: "POST",
    headers: {
        "Authorization": `Bearer ${API_TOKEN}`,
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        name: "Production Alerts",
        url: "https://yourapp.com/webhooks/dartnode",
        events: ["service.provisioned", "service.suspended", "invoice.paid"]
    })
});

const { data } = await resp.json();
console.log(`Webhook #${data.webhook.id} created with secret: ${data.webhook.secret}`);
                
Store the secret. It is returned only when the webhook is created. You'll need it to verify signatures on incoming deliveries.

Event Types

Subscribe to specific events to receive targeted notifications. Use * to receive all events.

Service Events

Event TypeDescription
service.provisionedA new service has been provisioned and is ready to use
service.suspendedA service has been suspended (usually for non-payment)
service.unsuspendedA suspended service has been reactivated
service.terminatedA service has been permanently terminated and data deleted
service.cancelledA cancellation request has been submitted for a service
service.deployedA new VM deployment has been initiated via the API

Power Events

Event TypeDescription
service.power.onA VM has been powered on
service.power.offA VM has been force stopped
service.power.shutdownA graceful shutdown has been initiated on a VM
service.power.rebootA VM has been rebooted

Billing Events

Event TypeDescription
invoice.createdA new invoice has been generated
invoice.paidAn invoice has been successfully paid
invoice.past_dueAn invoice is past its due date
refund.issuedA refund has been processed to the original payment method

Network Events

Event TypeDescription
ip.attachedAn IP address was attached to a service
ip.detachedAn IP address was detached from a service

Verifying Signatures

Every webhook delivery includes a cryptographic signature so you can verify it originated from DartNode and hasn't been tampered with.

Headers

X-DartNode-SignatureHMAC-SHA256 signature of the payload
X-DartNode-TimestampUnix timestamp used in signing
X-DartNode-EventThe event type (e.g. service.provisioned)

Signature Format

v1=HMAC-SHA256(timestamp.payload, secret_hash)

The signed payload is the concatenation of the timestamp, a dot (.), and the raw request body.

Verification Examples

Python
import hmac, hashlib, time

def verify_webhook(payload: bytes, signature: str,
                   timestamp: str, secret_hash: str) -> bool:
    # Reject deliveries older than 5 minutes (replay protection)
    if abs(time.time() - int(timestamp)) > 300:
        return False

    signed_payload = f"{timestamp}.".encode() + payload
    expected = "v1=" + hmac.new(
        secret_hash.encode(), signed_payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# Usage in Flask:
@app.route("/webhooks/dartnode", methods=["POST"])
def handle_webhook():
    if not verify_webhook(
        request.data,
        request.headers["X-DartNode-Signature"],
        request.headers["X-DartNode-Timestamp"],
        WEBHOOK_SECRET
    ):
        return "Invalid signature", 401

    event = request.headers["X-DartNode-Event"]
    data = request.json
    # Process event...
    return "OK", 200
Node.js
const crypto = require("crypto");

function verifyWebhook(payload, signature, timestamp, secretHash) {
    // Reject deliveries older than 5 minutes
    if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
        return false;
    }

    const signedPayload = `${timestamp}.${payload}`;
    const expected = "v1=" + crypto
        .createHmac("sha256", secretHash)
        .update(signedPayload)
        .digest("hex");
    return crypto.timingSafeEqual(
        Buffer.from(expected), Buffer.from(signature)
    );
}

// Usage in Express:
app.post("/webhooks/dartnode", express.raw({ type: "*/*" }), (req, res) => {
    const valid = verifyWebhook(
        req.body.toString(),
        req.headers["x-dartnode-signature"],
        req.headers["x-dartnode-timestamp"],
        WEBHOOK_SECRET
    );

    if (!valid) return res.status(401).send("Invalid signature");

    const event = req.headers["x-dartnode-event"];
    const data = JSON.parse(req.body);
    // Process event...
    res.sendStatus(200);
});
PHP
function verifyWebhook(
    string $payload, string $signature,
    string $timestamp, string $secretHash
): bool {
    // Reject deliveries older than 5 minutes
    if (abs(time() - (int) $timestamp) > 300) {
        return false;
    }

    $signedPayload = $timestamp . '.' . $payload;
    $expected = 'v1=' . hash_hmac('sha256', $signedPayload, $secretHash);
    return hash_equals($expected, $signature);
}

// Usage:
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DARTNODE_SIGNATURE'];
$timestamp = $_SERVER['HTTP_X_DARTNODE_TIMESTAMP'];

if (!verifyWebhook($payload, $signature, $timestamp, WEBHOOK_SECRET)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = $_SERVER['HTTP_X_DARTNODE_EVENT'];
$data = json_decode($payload, true);
// Process event...
http_response_code(200);
Security tip: Always verify both the signature and the timestamp. The timestamp check prevents replay attacks where an attacker resends a previously captured webhook delivery.

Payload Format

All webhook deliveries are HTTP POST requests with a JSON body. DartNode waits up to 10 seconds for a 2xx response.

Request Headers

Content-Typeapplication/json
User-AgentDartNode-Webhooks/1.0
X-DartNode-EventThe event type (e.g. service.provisioned)
X-DartNode-SignatureHMAC-SHA256 signature for verification
X-DartNode-TimestampUnix timestamp used in signing

Example: service.provisioned

JSON
{
    "id": "evt_a1b2c3d4e5f6",
    "type": "service.provisioned",
    "created_at": "2026-03-02T15:30:00+00:00",
    "data": {
        "service_id": 456,
        "service_type": "vps",
        "status": "active",
        "hostname": "my-server",
        "plan": 1,
        "region": "us-dal"
    }
}

Example: invoice.paid

JSON
{
    "id": "evt_f6e5d4c3b2a1",
    "type": "invoice.paid",
    "created_at": "2026-03-02T15:30:00+00:00",
    "data": {
        "invoice_id": 789,
        "status": "paid",
        "total": 9.95,
        "balance": 0,
        "items": [
            {
                "description": "Base Plan: Cloud VPS 2GB",
                "amount": 9.95,
                "quantity": 1
            }
        ],
        "customer_id": 123
    }
}
Delivery policy: DartNode expects a 2xx response within 10 seconds. After 10 consecutive delivery failures, the webhook is automatically disabled. You can re-enable it from the dashboard or via the API.

OAuth 2.0

Build third-party applications that access DartNode on behalf of users using the OAuth 2.0 Authorization Code Grant flow.

When to Use OAuth

  • Third-party integrations - Your app needs to manage DartNode resources for other users
  • Marketplace apps - Building a tool that multiple DartNode customers will use
  • Dashboards & monitoring - Creating a custom control panel for DartNode services

If you only need to access your own account, use Service Keys instead - they're simpler and don't require the OAuth flow.

Register an App

Create your OAuth application from the dashboard. You'll receive a client_id (prefixed dna_) and client_secret (prefixed dns_).

Keep your client_secret secure. Never expose it in client-side code, public repositories, or browser-accessible files. The token exchange must happen server-side.

Authorization Flow

The standard OAuth 2.0 Authorization Code flow in three steps.

Step 1: Redirect to Authorization

Redirect the user to the DartNode OAuth authorization page with your app details:

Authorization URL
https://oauth.dartnode.com/authorize
  ?client_id=dna_your_client_id
  &redirect_uri=https://yourapp.com/callback
  &scope=services.read services.power
  &response_type=code
  &state=random_csrf_token
ParameterRequiredDescription
client_idYesYour OAuth app's client ID (dna_ prefix)
redirect_uriYesMust exactly match one of your registered redirect URIs
response_typeYesMust be code
scopeNoSpace-separated list of requested scopes (defaults to app's registered scopes)
stateRecommendedRandom string for CSRF protection (verify on callback)
You can also use https://api.dartnode.net/oauth/authorize as an alternative entry point - it validates the request and redirects to the OAuth consent domain automatically.

Step 2: User Logs In & Approves

The user is taken to oauth.dartnode.com where they will:

  • Log in with their DartNode credentials (if not already authenticated)
  • Review the consent screen showing your app name and the scopes being requested
  • Approve or deny the authorization request

If the user approves, they are redirected back to your redirect_uri with an authorization code:

https://yourapp.com/callback?code=dnc_xxx&state=random_csrf_token

If the user denies the request:

https://yourapp.com/callback?error=access_denied&error_description=The+user+denied+the+request&state=random_csrf_token
Always verify the state parameter matches what you sent in Step 1. This prevents CSRF attacks where an attacker tricks a user into authorizing a malicious request.
Authorization codes (dnc_ prefix) expire after 10 minutes and are single-use. Exchange them for tokens immediately after receiving the callback.

Step 3: Exchange Code for Tokens

Exchange the authorization code for access and refresh tokens. This request must be made server-side.

cURL
curl -X POST https://api.dartnode.net/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=dnc_xxx" \
  -d "client_id=dna_your_client_id" \
  -d "client_secret=dns_your_client_secret" \
  -d "redirect_uri=https://yourapp.com/callback"
Python
resp = requests.post("https://api.dartnode.net/v1/oauth/token", data={
    "grant_type": "authorization_code",
    "code": "dnc_xxx",
    "client_id": "dna_your_client_id",
    "client_secret": "dns_your_client_secret",
    "redirect_uri": "https://yourapp.com/callback"
})

tokens = resp.json()["data"]
access_token = tokens["access_token"]   # dno_xxx (1 hour)
refresh_token = tokens["refresh_token"] # dnr_xxx (30 days)
Node.js
const resp = await fetch("https://api.dartnode.net/v1/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
        grant_type: "authorization_code",
        code: "dnc_xxx",
        client_id: "dna_your_client_id",
        client_secret: "dns_your_client_secret",
        redirect_uri: "https://yourapp.com/callback"
    })
});

const { data } = await resp.json();
const { access_token, refresh_token } = data;
PHP
$ch = curl_init("https://api.dartnode.net/v1/oauth/token");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        "grant_type" => "authorization_code",
        "code" => "dnc_xxx",
        "client_id" => "dna_your_client_id",
        "client_secret" => "dns_your_client_secret",
        "redirect_uri" => "https://yourapp.com/callback"
    ]),
    CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true)["data"];
$accessToken = $data["access_token"];
$refreshToken = $data["refresh_token"];

Token Response

JSON
{
    "success": true,
    "data": {
        "access_token": "dno_xxx",
        "refresh_token": "dnr_xxx",
        "expires_in": 3600,
        "token_type": "Bearer"
    }
}

Token Management

Access tokens expire after 1 hour. Use refresh tokens to obtain new access tokens without requiring the user to re-authorize.

Refreshing Tokens

Refresh tokens are valid for 30 days. Each refresh returns a new access token and a new refresh token (the old refresh token is invalidated).

cURL
curl -X POST https://api.dartnode.net/v1/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=dnr_xxx" \
  -d "client_id=dna_your_client_id" \
  -d "client_secret=dns_your_client_secret"

Token Lifecycle

Token TypeLifetimeRenewal
Access Token1 hourUse refresh token to get a new one
Refresh Token30 daysNew one issued with each refresh
Authorization Code10 minutesSingle use - exchange immediately
Best practice: Refresh the access token proactively before it expires (e.g., at the 50-minute mark) rather than waiting for a 401 error. This provides a smoother experience for users.