For utviklere

Bygg på CLK Cloud

Lag en nøkkel, start en server og reager på hendelser på få minutter. Utviklerdokumentasjonen er på engelsk.

Quickstart

Everything you can do in the console you can do with the API: it is the same service underneath. Base URL: https://api.cloud.clk.no/v1. JSON in, JSON out, TLS only.

1. Create a key

In the console, open API keys, pick a role (or scopes), a name and an optional expiry. The key is shown once.

export KEY=clk_live_…

2. Check it works and see what you can create

curl -H "Authorization: Bearer $KEY" https://api.cloud.clk.no/v1/me
curl -H "Authorization: Bearer $KEY" https://api.cloud.clk.no/v1/sizes
curl -H "Authorization: Bearer $KEY" https://api.cloud.clk.no/v1/images

3. Create a server

name, size and labels are required. network and sshKey can be left out when your organization has exactly one of each. Add your key with POST /v1/ssh-keys first.

curl -X POST https://api.cloud.clk.no/v1/servers \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "web-1",
    "size": "CLK Dedicated Extra Small",
    "labels": { "env": "prod" },
    "userData": "#cloud-config\npackages:\n  - docker.io\n"
  }'

# 202 Accepted
{ "jobId": "0b9c…", "jobUrl": "/v1/jobs/0b9c…" }

New accounts follow the same rules as in the console: the account must be approved, have a card, and stay within the starting limits.

Authentication

Send an organization API key as a bearer token: Authorization: Bearer clk_live_…. A key belongs to one organization, carries scopes, can expire, and can be revoked in the console. Keys start with clk_live_ so secret scanners can recognise a leak. Only a hash is stored, so a lost key has to be replaced.

Giving an AI agent access? Create it its own key with the narrowest role, a short expiry, and caps: a maximum number of servers and a maximum monthly cost, set when you create the key in the console. A create that would break a cap is refused with 422 key_limit_exceeded. Keys cannot change billing, members or other keys.

Roles

RoleScopes
read-onlyservers, ssh-keys, networks, jobs, events, webhooks, ips and volumes: read
operatorread-only, plus servers:write, ips:write and volumes:write
admineverything, including webhooks:write, ssh-keys:write and billing:read

Scopes

ScopeAllows
servers:readList and read servers, sizes, images and regions
servers:writeCreate, delete, start, stop, reboot and label servers
ssh-keys:read / writeList, add and remove SSH keys
networks:readList networks
jobs:readRead job status
events:readRead the event feed
webhooks:read / writeList, create and delete webhook endpoints
ips:read / writeList, allocate, attach, move and release public IPv4 addresses
volumes:read / writeVolumes and snapshots (when enabled)
billing:readCosts and invoices

Async operations

Create, delete, start, stop and reboot answer 202 Accepted with a job. Poll GET /v1/jobs/{id} until status is succeeded or failed (error says why), or subscribe to webhooks and skip polling.

curl -H "Authorization: Bearer $KEY" https://api.cloud.clk.no/v1/jobs/0b9c…
{ "id": "0b9c…", "status": "succeeded", "createdAt": "2026-10-08T12:00:00Z" }

curl -H "Authorization: Bearer $KEY" "https://api.cloud.clk.no/v1/servers?label=env:prod"

Servers can be filtered by label (?label=env:prod, repeat for AND), and costs grouped by label (/v1/usage?group_by=label:env), so labels are the way to split cost per team or project.

Webhooks

Register an https endpoint with POST /v1/webhooks (or in the console) and choose the event types. The response includes a signing secret, shown once. Endpoints must resolve to public addresses; private, loopback and link-local destinations are refused. Up to 10 endpoints per organization.

Delivery body
{
  "id": "5d0e…",
  "type": "server.created",
  "created_at": "2026-10-08T12:01:10Z",
  "organization_id": "a1b2…",
  "data": { … }
}

Each delivery is a POST with Content-Type: application/json, a 10 second timeout, and the headers CLK-Event-Id, CLK-Event-Type and CLK-Signature. Any 2xx counts as delivered. Failures are retried after 1, 5, 30 minutes and 2, 6 and 12 hours. Delivery is at least once: use the event id to ignore duplicates. A delivery log with replay is in the console.

Verify the signature

CLK-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of "t.body" with your secret. Compute it over the raw request bytes and reject old timestamps. While a secret is being rotated, the header carries a second v1 signed with the previous secret for 24 hours.

Node.js
import { createHmac, timingSafeEqual } from 'node:crypto'

// rawBody must be the exact bytes received, before any JSON parsing.
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const t = Number(parts.t)
  if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  // During a secret rotation the header holds two v1= values; accept either.
  return header.split(',').filter((p) => p.startsWith('v1=')).some((p) => {
    const got = p.slice(3)
    return got.length === expected.length && timingSafeEqual(Buffer.from(got), Buffer.from(expected))
  })
}
Python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    items = [p.split("=", 1) for p in header.split(",")]
    t = int(next(v for k, v in items if k == "t"))
    if abs(time.time() - t) > tolerance:
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return any(hmac.compare_digest(v, expected) for k, v in items if k == "v1")

Event types

AreaEvents
Serversserver.created · server.creation_failed · server.started · server.stopped · server.rebooted · server.deleted · server.action_failed
Accountaccount.approved · account.rejected · account.card_changed · account.closure_scheduled · account.closure_cancelled · account.suspended · account.resumed · account.blocked · account.unblocked
Networkingip.allocated · ip.released · firewall.rule_created · firewall.rule_deleted · port_forward.created · port_forward.deleted
Storagevolume.created · volume.attached · volume.detached · volume.deleted · snapshot.created · snapshot.deleted
Billinginvoice.created · invoice.paid · invoice.payment_failed · budget.threshold · credit.granted · coupon.redeemed · referral.signed_up · referral.rewarded
Platformannouncement.created · announcement.updated · announcement.resolved

Prefer polling? GET /v1/events?after=<last event id> returns the same events. Events and delivery logs are kept for 30 days.

CLI, Terraform and MCP

Three ready-made clients, all built on the same API. Source and downloads are in the clk-cli repository.

clk, the command line

export CLK_API_KEY=clk_live_…
clk servers create --name web-1 --size "CLK Dedicated Extra Small" --label env=prod --ttl 2h --wait
clk servers list --label env=prod
clk servers delete <id> --yes --wait
clk events --follow

Terraform

Resources clk_server, clk_ssh_key and clk_webhook. Only labels change in place; other changes replace the server.

resource "clk_ssh_key" "me" {
  name       = "laptop"
  public_key = file("~/.ssh/id_ed25519.pub")
}

resource "clk_server" "web" {
  name    = "web-1"
  size    = "CLK Dedicated Extra Small"
  ssh_key = clk_ssh_key.me.name
  labels  = { env = "prod" }
}

MCP server for AI agents

clk-mcp speaks the Model Context Protocol, so Claude and other agents can list sizes, create servers, read events and costs. Servers an agent creates are labelled created-by=mcp, it can only delete those, and CLK_MCP_READONLY=1 removes every tool that changes something.

claude mcp add clk -e CLK_API_KEY=clk_live_… -e CLK_MCP_DEFAULT_TTL=2h -- clk-mcp

AI agents

The API is built to be driven by agents as well as people. Give each agent its own key with the narrowest role (read-only or operator), a short expiry and caps, pass ttl when it creates a server so a forgotten server deletes itself, label everything it creates (for example agent=release-bot), and have it delete what it creates. Costs can then be split per agent with /v1/usage?group_by=label:agent, and the audit log shows what each key did.

For tools that read documentation, /llms.txt is an index and /llms-full.txt is the whole API in one plain-text file. The machine-readable OpenAPI spec is linked below.

Errors and limits

Every error has the same shape. Quote the request_id (also in the X-Request-Id header on every response) when you write to support.

HTTP/1.1 403 Forbidden
X-Request-Id: 6f1c…

{ "error": { "code": "forbidden", "message": "this key lacks the scope servers:write", "request_id": "6f1c…" } }
StatuscodeMeaning
400invalid_requestBad or missing field; the message says which
401unauthorizedKey missing, invalid, expired or revoked
403forbiddenThe key lacks the scope this endpoint needs
422key_limit_exceededThe key's own server or monthly cost cap would be exceeded
429rate_limitedToo many requests; wait Retry-After seconds

Rate limit: 120 requests per minute per key. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds). Other limits: user data up to 16 KB, up to 20 labels per server.

Error codes are stable; messages are for humans and may change.

Versioning

/v1 only changes in backwards compatible ways: new endpoints, optional request fields, response fields, event types and enum values can appear at any time, so ignore what you don't recognise. A breaking change ships as /v2, and /v1 keeps working for at least 12 months after that is announced. Deprecations are announced at least 6 months ahead, and the endpoint then answers with Deprecation and Sunset headers.

Every endpoint is listed in the API reference. The machine-readable OpenAPI spec is at https://api.cloud.clk.no/swagger/v3/api-docs/v1.