Skip to content

API Reference ​

Base URL: https://api.computeportal.io

The ComputePortal REST API gives programmatic control over every aspect of your GPU compute environment. All requests are authenticated with a Bearer token.


Authentication ​

Every request must include your CP API token:

http
Authorization: Bearer cp_live_<hex>
bash
curl https://api.computeportal.io/v1/services \
  -H "Authorization: Bearer cp_live_4a7f3b9c2e1d8a06"

Get your token from the CP Dashboard under Settings → API Tokens, or via cpctl whoami.

Unauthenticated endpoints ​

A small number of endpoints are public (no token required). These are called out explicitly in each section.


Rate Limits ​

ScopeLimit
Authenticated requests120 per 60 seconds per token
Public provision/redeem endpoints5 per 60 seconds per IP

Responses include standard rate-limit headers:

http
RateLimit-Limit: 120
RateLimit-Remaining: 119
RateLimit-Reset: 1722345678

On exhaustion: 429 Too Many Requests with Retry-After header.


Errors ​

All error responses use a consistent JSON shape:

json
{
  "error": "human-readable description",
  "code": "MACHINE_READABLE_CODE"
}
StatusCodeDescription
400BAD_REQUESTInvalid request (e.g. CNAME not configured, default domain removal)
401UNAUTHORIZEDMissing, expired, or invalid token
402QUOTA_EXCEEDEDCPU/memory/GPU quota exceeded for your plan
403FORBIDDENToken lacks permission for this resource
404NOT_FOUNDResource does not exist or is not owned by this account
409CONFLICTResource already exists or bucket not empty
410GONEToken expired or already used
422VALIDATION_ERRORRequest body failed schema validation
429RATE_LIMITEDRate limit exceeded
500INTERNAL_ERRORUnexpected server error

Catalog ​

The SKU catalog and x402 per-second pricing are the two machine-readable price feeds. Both are public — no Authorization header required — so they can be scraped by comparison tools or consumed by agents at planning time.

GET /v1/catalog ​

Returns the full compute-plan SKU catalog. One entry per plan tier; machine_type is non-null only for SKUs that also have an x402 per-second row.

Response 200

json
{
  "catalog": [
    {
      "id": "basic-1-2-50",
      "category": "basic",
      "vcpu": 1,
      "memory_gib": 2,
      "storage_gib": 50,
      "gpu": null,
      "gpu_memory_gib": null,
      "price_per_month": 11.64,
      "machine_type": null
    },
    {
      "id": "gpu-16-80-h100",
      "category": "gpu",
      "vcpu": 16,
      "memory_gib": 80,
      "storage_gib": 500,
      "gpu": "NVIDIA H100 PCIe",
      "gpu_memory_gib": 80,
      "price_per_month": 4122.13,
      "machine_type": "gpu-h100"
    }
  ]
}
FieldMeaning
idSKU identifier (same string used by cpctl pay, cp_catalog_list)
categoryOne of basic, cpu-optimized, general-purpose, high-memory, gpu
price_per_monthUSD, prepaid, auto-renews — plans are not per-hour billed
machine_typeJoin key to /x402/pricing; null when no x402 rate is published

Also surfaced to agents as MCP tool cp_catalog_list.


GET /x402/pricing ​

Returns the x402 pay-per-second rate table used by the EIP-3009 payment flow. Validated against schemas/x402-pricing.schema.json.

Response 200

json
{
  "chain": "base-sepolia",
  "asset": "USDC",
  "pay_to": "0x2638cA545a547254e831dB2835981649323e1f79",
  "pricing": [
    { "machine_type": "cpu-small", "usd_per_second": "0.0003",  "max_authorization_usd": "1.00"  },
    { "machine_type": "cpu-med",   "usd_per_second": "0.0010",  "max_authorization_usd": "5.00"  },
    { "machine_type": "gb10",      "usd_per_second": "0.00116", "max_authorization_usd": "17.00" },
    { "machine_type": "gpu-h100",  "usd_per_second": "0.0220",  "max_authorization_usd": "25.00" }
  ],
  "exact_items": [
    { "sku": "quote.cpu-small",     "usd": "0.10" },
    { "sku": "reserve.1h.gpu-h100", "usd": "79.20" }
  ],
  "generated_at": "2026-10-26T00:00:00Z"
}

Mainnet status: chain is base-sepolia today. Mainnet USDC cutover is gated on CDP KYB clearance — the pricing feed will be re-published with chain: "base" once the switch is approved.


Services ​

Services are Kubernetes Deployments running on CP compute infrastructure.

GET /v1/services ​

List all services in your account.

bash
curl https://api.computeportal.io/v1/services \
  -H "Authorization: Bearer cp_live_..."

Response 200

json
[
  {
    "name": "ml-inference",
    "image": "myorg/inference:v2",
    "status": "running",
    "region": "eu-west",
    "node": "node-eu1-001",
    "cpu": "2",
    "memory": "8Gi",
    "gpu": "rtx5090",
    "url": "https://ml-inference.cpctl.app",
    "port": 8080,
    "replicas": 2,
    "desired_replicas": 2,
    "autoscaling": { "enabled": false },
    "created_at": "2026-07-01T08:00:00Z"
  }
]

POST /v1/services ​

Deploy a new service from a Docker image or GitHub repo.

Request Body

FieldTypeRequiredDescription
imagestringYes (or repo)Docker image to deploy
repostringYes (or image)GitHub repo URL to build and deploy
namestringNoService name (derived from image/repo if omitted)
branchstringNoGit branch (repo deploys only)
regionstringNoeu-west, us-east, or ap-south
gpustringNoGPU type: gb10, rtx5090
node_idstringNoPin to a specific node ID
replicasintegerNoInitial replica count (default: 1)
portintegerNoContainer port (auto-detected from image EXPOSE if omitted)
env_varsobjectNoEnvironment variables to set at deploy time
sub_pathstringNoSubdirectory within the repo to build
no_probebooleanNoDisable readiness probe (for workers/batch jobs)
health_checkobjectNoReadiness probe config (see below)
init_imagestringNoInit container image — runs to completion before the main container starts
init_commandstringNoShell command for the init container
init_envstring[]NoEnv vars for the init container, each "KEY=VALUE"
mount_configmapstring[]NoConfigMaps to mount, each "alias:mountPath" (e.g. "app-config:/data/config")

health_check object

FieldTypeValuesDescription
typestringnone, tcp, execProbe type. none = no probe; tcp = TCP socket (default); exec = run a command
commandstring[]—Required when type is exec
bash
curl -X POST https://api.computeportal.io/v1/services \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "image": "myorg/inference:v2",
    "name": "ml-inference",
    "gpu": "rtx5090",
    "replicas": 2,
    "env_vars": { "LOG_LEVEL": "info" }
  }'

Response 200

json
{
  "service_id": "ml-inference",
  "name": "ml-inference",
  "url": "https://ml-inference.cpctl.app",
  "status": "starting",
  "port": 8080,
  "port_source": "image"
}

port_source values:

ValueMeaning
explicitSet via --port / port request field
imageDetected from image EXPOSE
injectedAssigned by CP; app must read process.env.PORT

Error Responses

StatusCondition
402Quota exceeded — insufficient CPU/memory/GPU
422Neither image nor repo provided

GET /v1/services/precheck ​

Side-effect-free pre-flight for POST /v1/services. Returns whether the requested shape would be admitted now and the account's estimated monthly plan cost. Used by cpctl deploy --dry-run.

Query parameters

ParamTypeDescription
gpustringGPU type the deploy would request (empty for CPU-only)
replicasintegerReplica count (default 1)
bash
curl 'https://api.computeportal.io/v1/services/precheck?gpu=&replicas=1' \
  -H "Authorization: Bearer cp_live_..."

Response 200

json
{
  "can_deploy": true,
  "reason": null,
  "hint": null,
  "entitlement": {
    "vcpu_milli": 4000,
    "memory_mib": 16384,
    "gpu_count": 0,
    "active_plans": ["gp-4-16-50"]
  },
  "reserved":  { "vcpu_milli": 100, "memory_mib": 128, "gpu_count": 0 },
  "requested": { "vcpu_milli": 100, "memory_mib": 128, "gpu_count": 0 },
  "estimated_cost_usd_per_month": 109.56
}

When the shape would be rejected, can_deploy is false, reason and hint are populated, and the response is still 200 (the endpoint is informational only — the actual gate runs on POST /v1/services).


GET /v1/services/:name ​

Get details for a specific service.

bash
curl https://api.computeportal.io/v1/services/ml-inference \
  -H "Authorization: Bearer cp_live_..."

Response 200 — same shape as a single item from GET /v1/services.


PATCH /v1/services/:name ​

Update a running service's image, env vars, or replica count. Triggers a rolling restart.

Request Body

FieldTypeDescription
imagestringNew container image
env_varsobjectEnv vars to merge (does not replace existing keys)
replicasintegerNew replica count
no_probebooleanEnable/disable readiness probe
health_checkobjectNew probe config
bash
curl -X PATCH https://api.computeportal.io/v1/services/ml-inference \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "image": "myorg/inference:v3", "replicas": 3 }'

Response 200

json
{ "ok": true, "name": "ml-inference" }

DELETE /v1/services/:name ​

Delete a service and all associated Kubernetes resources (Deployment, Service, Ingress).

bash
curl -X DELETE https://api.computeportal.io/v1/services/ml-inference \
  -H "Authorization: Bearer cp_live_..."

Response 204 No Content

Dispatches a service.destroyed webhook event.


POST /v1/services/:name/start ​

Start a stopped service (sets replicas to 1).

Response 200 { "ok": true }


POST /v1/services/:name/stop ​

Stop a running service (sets replicas to 0, preserves config).

Response 200 { "ok": true }


POST /v1/services/:name/restart ​

Rolling restart — all pods replaced one by one. Current replica count is preserved.

Response 200 { "ok": true }


POST /v1/services/:name/redeploy ​

Re-trigger the current deployment without changing image or configuration.

Response 200 { "ok": true }


POST /v1/services/:name/rollback ​

Roll back to the previous successful deployment.

Response 200

json
{
  "ok": true,
  "rolledBackTo": "myorg/inference:v1",
  "deployedAt": "2026-06-28T09:15:00Z"
}
StatusCondition
400Fewer than 2 successful deployments in history

POST /v1/services/:name/scale ​

Scale replicas or adjust CPU/memory resource limits.

Request Body

FieldTypeDescription
replicasintegerTarget replica count
cpustringCPU limit (e.g. "2", "500m")
memorystringMemory limit (e.g. "4Gi", "512Mi")

Response 200

json
{ "name": "ml-inference", "replicas": 3, "cpu": "2", "memory": "8Gi" }
StatusCondition
402Quota exceeded

GET /v1/services/:name/autoscale ​

Get the current HPA (Horizontal Pod Autoscaler) configuration.

Response 200

json
{
  "autoscaling": {
    "enabled": true,
    "min_replicas": 2,
    "max_replicas": 10,
    "cpu_threshold_pct": 70,
    "memory_threshold_pct": null,
    "current_replicas": 4
  }
}

POST /v1/services/:name/autoscale ​

Configure CPU/memory-triggered autoscaling (Kubernetes HPA).

Request Body

FieldTypeRequiredDescription
max_replicasintegerYesUpper bound on replica count
min_replicasintegerNoLower bound (default: 1)
cpu_threshold_pctintegerNoCPU utilisation % to trigger scale-up (default: 80)
memory_threshold_pctintegerNoMemory utilisation % to trigger scale-up
bash
curl -X POST https://api.computeportal.io/v1/services/ml-inference/autoscale \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "min_replicas": 2, "max_replicas": 10, "cpu_threshold_pct": 70 }'

Response 200

json
{
  "ok": true,
  "autoscaling": {
    "enabled": true,
    "min_replicas": 2,
    "max_replicas": 10,
    "cpu_threshold_pct": 70
  }
}
StatusCondition
422max_replicas missing or less than 1

DELETE /v1/services/:name/autoscale ​

Disable autoscaling and remove the HPA.

Response 200 { "ok": true }


GET /v1/services/:name/logs ​

Fetch recent log output from a service.

Query Parameters

ParameterDefaultDescription
tail100Number of recent lines
followfalseStream in real time (SSE)
bash
curl "https://api.computeportal.io/v1/services/ml-inference/logs?tail=50&follow=true" \
  -H "Authorization: Bearer cp_live_..."

Response 200 — plain text log lines. When follow=true, streamed as server-sent events until the connection is closed.


GET /v1/services/:name/build-logs ​

Stream Kaniko build logs for a repo-based deployment. Returns an SSE stream; terminates with a JSON trailer line:

[build-result] {"buildStatus":"success","exitCode":0}
buildStatusMeaning
successImage built and pushed
failedBuild failed — see preceding log lines

GET /v1/services/:name/metrics ​

Get CPU, memory, and network metrics for a service.

Response 200

json
{
  "cpu_usage": "34%",
  "memory_usage": "4.2 GiB / 16 GiB",
  "network_rx": "0.4 MB/s",
  "network_tx": "1.2 MB/s",
  "replicas": 2
}

GET /v1/services/:name/gpu ​

Get GPU utilization metrics for a service.

Response 200

json
{
  "service": "ml-inference",
  "gpu_type": "gb10",
  "util_pct": 72,
  "vram_used_gb": 14.0,
  "vram_total_gb": 24.0,
  "temp_c": 67,
  "power_w": 280,
  "node": "node-eu1-001"
}

GET /v1/services/:name/deployments ​

List deployment history for a service.

Response 200

json
[
  {
    "id": "deploy_abc123",
    "image": "myorg/inference:v2",
    "deployedAt": "2026-07-08T08:00:00Z",
    "status": "success"
  },
  {
    "id": "deploy_xyz789",
    "image": "myorg/inference:v1",
    "deployedAt": "2026-06-28T09:15:00Z",
    "status": "success"
  }
]

GET /v1/services/:name/deployments/:id ​

Get details for a specific deployment.

Response 200 — single deployment entry from the list above.


GET /v1/services/:name/events ​

List recent Kubernetes events for a service — useful for diagnosing startup failures.

Response 200

json
[
  {
    "reason": "Failed",
    "message": "Error: ImagePullBackOff",
    "type": "Warning",
    "timestamp": "2026-08-10T08:45:34Z",
    "count": 12
  }
]

Capped at 20 events, sorted by timestamp descending.


GET /v1/services/:name/env ​

List all environment variables on a service.

Response 200

json
{
  "MODEL_PATH": "/models/v2",
  "LOG_LEVEL": "info"
}

POST /v1/services/:name/env ​

Set environment variables. Merges with existing vars (does not replace). Triggers a rolling restart.

Values may reference another service's live URL using the syntax ${{SERVICE_NAME.URL}} — resolved at set-time to the target service's public URL.

Request Body — flat key-value object:

bash
curl -X POST https://api.computeportal.io/v1/services/ml-inference/env \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "MODEL_PATH": "/models/v3", "LOG_LEVEL": "debug" }'

Response 200 { "ok": true }


POST /v1/services/:name/env/unset ​

Remove environment variables. Triggers a rolling restart.

Request Body

FieldTypeDescription
keysstring[]Variable names to remove

Response 200 { "ok": true }


POST /v1/services/:name/link ​

Wire two services together by injecting each other's public URLs as environment variables.

Request Body

FieldTypeRequiredDescription
targetstringYesTarget service name
bidirectionalbooleanNoAlso inject source URL into target (default: true)

Response 200

json
{
  "ok": true,
  "linked": ["tfx-frontend", "tfx-backend"],
  "injected": {
    "TFX_BACKEND_URL": "https://tfx-backend.cpctl.app",
    "BACKEND_URL": "https://tfx-backend.cpctl.app"
  },
  "note": "Redeploy both services for the new env vars to take effect"
}

POST /v1/services/:name/clone ​

Clone a service under a new name. Copies image, resource configuration, and (optionally) env vars.

Request Body

FieldTypeRequiredDescription
target_namestringYesNew service name
include_envbooleanNoCopy environment variables (default: true)

Response 200

json
{ "ok": true, "name": "ml-inference-staging", "url": "https://ml-inference-staging.cpctl.app", "cloned_from": "ml-inference" }

POST /v1/services/:name/env/copy ​

Copy all environment variables from this service to another. Target's existing values win on conflict.

Request Body

FieldTypeRequiredDescription
target_servicestringYesDestination service name

Response 200

json
{ "ok": true, "copied": 5, "target": "ml-inference-staging" }

POST /v1/services/:name/exec ​

Get a WebSocket URL for running a command inside a container.

Request Body

FieldTypeDescription
commandstring[]Command to run (default: ["sh"])

Response 200

json
{
  "exec_url": "wss://api.computeportal.io/v1/services/ml-inference/exec/ws",
  "pod_name": "ml-inference-abc123",
  "namespace": "compute-portal"
}

Connect to exec_url with your token in the Authorization header. Frames are multiplexed: 0x00 = stdin, 0x01 = stdout, 0x02 = stderr.


GET /v1/services/:name/volumes ​

List persistent volumes attached to a service.

Response 200

json
[
  { "name": "pvc-my-svc-1234567890", "claim": "pvc-my-svc-1234567890", "mount": "/data", "size": "10Gi", "created_at": "2026-07-01T08:00:00Z" }
]

POST /v1/services/:name/volumes ​

Attach a new persistent volume to a service. Creates a PersistentVolumeClaim and mounts it into the running Deployment.

Request Body

FieldTypeRequiredDescription
mountstringNoMount path inside the container (default: /data)
size_giintegerNoVolume size in GiB (default: 10)

Response 200

json
{ "name": "pvc-my-svc-1234567890", "mount": "/data", "service": "my-svc", "size_gi": 10 }

DELETE /v1/services/:name/volumes/:volName ​

Detach a persistent volume from a service. Removes the mount from the Deployment but retains the PVC — data is preserved and can be reattached later.

Response 200

json
{ "deleted": "pvc-my-svc-1234567890" }
StatusCondition
404Volume not found

POST /v1/services/:name/tcp-proxy ​

Expose a non-HTTP container port externally via a NodePort.

Request Body

FieldTypeRequiredDescription
portintegerYesContainer port to expose

Response 200

json
{
  "host": "tcp.cpctl.app",
  "port": 32541,
  "target_port": 5432,
  "service": "ml-inference"
}

GET /v1/services/:name/tcp-proxy ​

List all TCP proxies for a service.

Response 200

json
[
  { "name": "ml-inference-tcp-5432", "host": "tcp.cpctl.app", "port": 32541, "target_port": 5432 }
]

DELETE /v1/services/:name/tcp-proxy/:port ​

Remove a TCP proxy by container port.

Response 204 No Content


TCP Expose ​

The TCP Expose system routes raw TCP traffic from a public port on tcp.cpctl.app to a container port on a service. Unlike the tcp-proxy endpoints (which create only a Kubernetes NodePort), expose also writes nginx stream configuration across all bare-metal nodes so traffic is reachable at the public tcp.cpctl.app:<port> address.

Use this for protocols that require a stable public hostname and port — Cardano P2P, PostgreSQL, custom binary protocols. Up to 5 exposed ports per account.

POST /v1/services/:name/expose ​

Expose a container port publicly over raw TCP.

Request Body

FieldTypeRequiredDescription
portintegerYesContainer port to expose (1024–49151)
bash
curl -X POST https://api.computeportal.io/v1/services/cardano-relay/expose \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "port": 3001 }'

Response 201

json
{
  "host": "tcp.cpctl.app",
  "port": 3001,
  "node_port": 30283,
  "created_at": "2026-09-05T08:00:00Z"
}
StatusCondition
409Port already exposed by another service
422Port out of range (must be 1024–49151)
4295-port per-account limit reached

GET /v1/services/:name/expose ​

List all exposed TCP ports for a service.

Response 200

json
[
  { "host": "tcp.cpctl.app", "port": 3001, "node_port": 30283, "created_at": "2026-09-05T08:00:00Z" }
]

DELETE /v1/services/:name/expose/:port ​

Remove a public TCP expose by container port. Removes the nginx stream config from all nodes and deletes the Kubernetes NodePort Service.

Response 204 No Content

StatusCondition
404Port not exposed for this service

POST /v1/services/:name/static-ip ​

Request a static outbound IP for a service.

Response 200

json
{ "ok": true, "ip": null, "status": "provisioning" }

DELETE /v1/services/:name/static-ip ​

Release the static outbound IP.

Response 200 { "ok": true }


GET /v1/status ​

Platform health check — does not require authentication.

Response 200

json
{
  "status": "healthy",
  "version": "v0.3.0",
  "services": 42,
  "nodes": 6,
  "orchestrator": "kubernetes",
  "gpu": { "available": 12, "total": 16 }
}

Domains ​

Custom domains serve your service under your own hostname with automatic TLS (Let's Encrypt via HTTP-01). The default <service>.cpctl.app domain is always available and cannot be removed.

Before attaching a domain, create a CNAME record at your DNS provider:

FieldValue
NameYour subdomain (e.g. api)
TypeCNAME
Targetcpctl.app
ProxyDNS only — disable Cloudflare orange-cloud

POST /v1/services/:name/domains ​

Attach a custom hostname. CP validates the CNAME, adds the hostname to the Traefik Ingress, and provisions TLS asynchronously.

Request Body

FieldTypeRequiredDescription
hostnamestringYesFully-qualified domain (e.g. api.example.com)
skip_dnsbooleanNoSkip CNAME validation (use when DNS hasn't propagated yet)
bash
curl -X POST https://api.computeportal.io/v1/services/my-api/domains \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "hostname": "api.example.com" }'

Response 200

json
{
  "domain": "api.example.com",
  "service": "my-api",
  "status": "provisioning",
  "tls": false,
  "created_at": "2026-07-04T10:52:36Z"
}

TLS is provisioned asynchronously — poll GET /v1/services/:name/domains/:hostname until tls: true.

StatusCondition
400CNAME not pointing to cpctl.app
422Invalid hostname format

GET /v1/services/:name/domains ​

List all hostnames attached to a service.

Response 200

json
[
  {
    "name": "my-api.cpctl.app",
    "service": "my-api",
    "status": "active",
    "tls": "active",
    "dns_configured": true,
    "cname_target": "cpctl.app",
    "created_at": ""
  },
  {
    "name": "api.example.com",
    "service": "my-api",
    "status": "active",
    "tls": "active",
    "dns_configured": true,
    "cname_target": "cpctl.app",
    "created_at": "2026-07-04T10:52:36.000Z"
  }
]

tls is the string "active" when TLS is provisioned.


GET /v1/services/:name/domains/:hostname ​

Get TLS and DNS status for a specific hostname.

Response 200

json
{
  "domain": "api.example.com",
  "service": "my-api",
  "status": "active",
  "tls": true,
  "cert_verified": true,
  "dns_configured": true,
  "cname_target": "cpctl.app",
  "created_at": "2026-07-04T10:52:36Z"
}

DELETE /v1/services/:name/domains/:hostname ​

Remove a custom hostname. TLS certificate is deprovisioned asynchronously.

Response 204 No Content

StatusCondition
400Attempt to remove the default <service>.cpctl.app domain
404Hostname not attached

Deploy Hooks ​

Deploy hooks are inbound HTTP endpoints that trigger a redeployment when called — useful for CI/CD systems that don't have a CP token.

POST /v1/services/:name/hooks ​

Create a new deploy hook for a service.

Response 200

json
{
  "id": "hook_abc123",
  "url": "https://api.computeportal.io/v1/hooks/hook_abc123",
  "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Trigger the hook by POSTing to the url with the secret in the X-CP-Hook-Secret header.


GET /v1/services/:name/hooks ​

List all deploy hooks for a service.

Response 200

json
[
  { "id": "hook_abc123", "url": "https://api.computeportal.io/v1/hooks/hook_abc123", "created_at": "2026-07-01T10:00:00Z" }
]

DELETE /v1/services/:name/hooks/:id ​

Delete a deploy hook.

Response 200 { "ok": true }


POST /v1/hooks/:id (public, secret-authenticated) ​

Trigger a redeployment via a deploy hook. No Bearer token required.

Auth: Pass the hook secret in the X-CP-Hook-Secret header.

Rate limit: 5 per 60 seconds per IP.

bash
curl -X POST https://api.computeportal.io/v1/hooks/hook_abc123 \
  -H "X-CP-Hook-Secret: whsec_xxxxxxxxxxxxxxxx"

Response 200

json
{ "ok": true, "service": "my-api" }

Also dispatches a deploy.started webhook event.


Outbound Webhooks ​

Subscribe to CP events and receive HTTP POST notifications at your own URL. Payloads are HMAC-SHA256 signed via X-CP-Signature.

Available events: deploy.succeeded, deploy.failed, service.crashed, service.stopped, service.destroyed, deploy.started

POST /v1/webhooks ​

Register an outbound webhook.

Request Body

FieldTypeRequiredDescription
urlstringYesHTTPS endpoint to receive events
eventsstring[]YesEvent types to subscribe to
bash
curl -X POST https://api.computeportal.io/v1/webhooks \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/cp", "events": ["deploy.succeeded", "deploy.failed"] }'

Response 200

json
{
  "id": "wh_abc123",
  "url": "https://hooks.example.com/cp",
  "events": ["deploy.succeeded", "deploy.failed"],
  "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxx"
}

Verify incoming payloads by comparing X-CP-Signature with HMAC-SHA256(secret, body).


GET /v1/webhooks ​

List all outbound webhooks.

Response 200

json
[
  { "id": "wh_abc123", "url": "https://hooks.example.com/cp", "events": ["deploy.succeeded"], "created_at": "2026-07-01T10:00:00Z" }
]

DELETE /v1/webhooks/:id ​

Delete an outbound webhook.

Response 200 { "ok": true }


Firewall ​

IP-based firewall rules applied at the Traefik Ingress layer — traffic is blocked before reaching the container.

POST /v1/services/:name/firewall/rules ​

Add a firewall rule. Each service supports up to 50 rules.

Request Body

FieldTypeRequiredDescription
modestringYesdeny — block a CIDR; allow — restrict access to a CIDR only
cidrstringYesIP or CIDR range (e.g. 1.2.3.4/32, 10.0.0.0/8)
bash
curl -X POST https://api.computeportal.io/v1/services/my-api/firewall/rules \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "mode": "deny", "cidr": "198.51.100.0/24" }'

Response 200

json
{
  "id": "deny:198.51.100.0/24",
  "mode": "deny",
  "cidr": "198.51.100.0/24",
  "service": "my-api",
  "created_at": "2026-07-08T10:00:00Z"
}
StatusCondition
422Invalid mode or cidr
42950-rule limit reached

GET /v1/services/:name/firewall/rules ​

List all firewall rules for a service.

Response 200

json
[
  { "id": "deny:198.51.100.0/24", "mode": "deny", "cidr": "198.51.100.0/24" }
]

DELETE /v1/services/:name/firewall/rules/:rule ​

Remove a single rule. The :rule path segment is the rule id (e.g. deny:198.51.100.0%2F24).

Response 204 No Content


DELETE /v1/services/:name/firewall ​

Remove all firewall rules from a service (disables firewall entirely).

Response 204 No Content


Preview Environments ​

PR preview environments automatically deploy a temporary copy of a service for each GitHub pull request and destroy it when the PR closes.

POST /v1/services/:name/previews/enable ​

Enable PR preview environments for a service.

Response 200

json
{ "ok": true, "previews_enabled": true }

POST /v1/services/:name/previews/disable ​

Disable PR preview environments.

Response 200 { "ok": true }


GET /v1/services/:name/previews ​

List active preview deployments.

Response 200

json
[
  {
    "name": "my-api-pr-42",
    "pr_number": 42,
    "branch": "feature/new-endpoint",
    "status": "running",
    "replicas": 1,
    "created_at": "2026-07-08T10:00:00Z"
  }
]

GPU & Nodes ​

GET /v1/gpu/nodes ​

List GPU nodes and current availability.

Query Parameters

ParameterDescription
regionFilter by region ID
typeFilter by GPU type (gb10, rtx5090)

Response 200

json
[
  {
    "node_name": "node-eu1-001",
    "gpu_type": "rtx5090",
    "gpu_count": 8,
    "available": 6,
    "region": "eu-west",
    "util_pct": 18,
    "vram_used_gb": 12.4,
    "vram_total_gb": 64.0,
    "temp_c": 67,
    "power_w": 280
  }
]

POST /v1/gpu/reserve ​

Reserve GPU capacity at a fixed rate.

Request Body

FieldTypeDescription
typestringGPU type: gb10, rtx5090
countintegerNumber of GPUs (default: 1)
regionstringRegion
durationstringDuration string (e.g. 1h, 24h, 7d)

Response 200

json
{
  "reservation_id": "res_abc123",
  "gpu_type": "rtx5090",
  "count": 2,
  "region": "eu-west",
  "reserved_until": "2026-07-09T08:00:00Z",
  "cost_per_hour": 2.40
}

cost_per_hour is a projection of your monthly plan rate split over the reservation window. You are not charged hourly — the plan is prepaid monthly. See pricing model.


POST /v1/gpu/jobs ​

Run a one-shot GPU job — billed from your monthly prepaid balance, auto-cleaned up when the container exits.

Response 200

json
{ "job_id": "job_abc123", "status": "queued", "started_at": "2026-07-08T09:00:00Z" }

GET /v1/gpu/jobs/:id/logs ​

Get log output for a GPU job.

Response 200 — plain text log lines.


GET /v1/nodes ​

List all compute nodes.

Query Parameters — region, gpu (filter by GPU type)

Response 200

json
[
  {
    "id": "abc123-uid",
    "name": "cp-k8s-worker-001",
    "hostname": "cp-k8s-worker-001",
    "status": "ready",
    "availability": "active",
    "role": "worker",
    "cpu": "64",
    "memory": "256Gi",
    "memory_gb": "256",
    "region": "eu-west",
    "gpu": "rtx5090",
    "gpu_count": 8,
    "reserved": false
  }
]

status is "ready" or "not-ready". availability is "active" or "drain". memory_gb is a string.


GET /v1/nodes/:id ​

Get hardware specs and status for a specific node.

Response 200 — single node object from the list above.


POST /v1/nodes/:id/reserve ​

Reserve an entire node for dedicated use.

Request Body

FieldTypeDescription
durationstringReservation duration (e.g. 1h, 24h, 7d)

Response 200

json
{ "node": "node-eu1-001", "reserved_until": "2026-07-09T08:00:00Z", "cost_per_hour": 8.00 }

cost_per_hour is a projection of your monthly plan rate split over the reservation window. You are not charged hourly — the plan is prepaid monthly. See pricing model.


POST /v1/nodes/:id/release ​

Release a reserved node back to the shared pool.

Response 200 { "ok": true }


GET /v1/regions ​

List available CP regions.

Response 200

json
[
  { "id": "eu-west",  "name": "EU West",  "location": "Amsterdam", "status": "available" },
  { "id": "us-east",  "name": "US East",  "location": "New York",  "status": "available" },
  { "id": "ap-south", "name": "AP South", "location": "Singapore", "status": "available" }
]

Databases ​

Managed databases run as StatefulSets with persistent storage. Connection strings use cluster-internal DNS — traffic stays within the cluster.

GET /v1/databases ​

List all databases in your account.

Response 200

json
[
  {
    "name": "app-db",
    "engine": "postgres",
    "version": "16",
    "status": "running",
    "region": "eu-west",
    "size_gb": 50,
    "connection_string": "postgres://admin:secret@db-app-db.compute-portal.svc.cluster.local:5432/app-db",
    "created_at": "2026-06-15T10:00:00Z"
  }
]

POST /v1/databases ​

Create a managed database.

Request Body

FieldTypeRequiredDescription
namestringYesDatabase name
enginestringNopostgres, mysql, redis, mongo (default: postgres)
regionstringNoRegion (default: account default)
size_gbintegerNoStorage in GB (default: 10)
bash
curl -X POST https://api.computeportal.io/v1/databases \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "app-db", "engine": "postgres", "size_gb": 50 }'

Response 200

json
{
  "name": "app-db",
  "engine": "postgres",
  "region": "eu-west",
  "size_gb": 50,
  "status": "starting",
  "connection_string": "postgres://admin:secret@db-app-db.compute-portal.svc.cluster.local:5432/app-db",
  "password": "secret"
}

GET /v1/databases/:name ​

Get details and connection info for a specific database.

Response 200 — single database object from the list.


POST /v1/databases/:name/stop ​

Scale the database StatefulSet to zero replicas (pause without deleting data).

Response 200 { "ok": true }


POST /v1/databases/:name/start ​

Restart a stopped database (scale replicas back to 1).

Response 200 { "ok": true }


POST /v1/databases/:name/attach ​

Inject connection environment variables into a service using the cluster-internal DNS name.

Request Body

FieldTypeRequiredDescription
servicestringYesTarget service name

Response 200

json
{
  "ok": true,
  "database": "app-db",
  "service": "my-app",
  "injected": {
    "DATABASE_URL": "postgres://admin:secret@db-app-db.compute-portal.svc.cluster.local:5432/app-db",
    "POSTGRES_URL": "postgres://admin:secret@db-app-db.compute-portal.svc.cluster.local:5432/app-db",
    "APP_DB_URL": "postgres://admin:secret@db-app-db.compute-portal.svc.cluster.local:5432/app-db"
  }
}
KeyDescription
DATABASE_URLGeneric — works with most ORMs
<ENGINE>_URLEngine-specific (POSTGRES_URL, REDIS_URL, MYSQL_URL, MONGO_URL)
<DB_NAME>_URLNamed key derived from the database name

DELETE /v1/databases/:name ​

Destroy a database and all its data. Irreversible.

Response 204 No Content


Object Storage ​

S3-compatible storage backed by MinIO. Endpoint: https://s3-cli.computeportal.io. Buckets are scoped per user.

POST /v1/storage/buckets ​

Create a new bucket.

Request Body

FieldTypeRequiredDescription
namestringYesBucket name (3–42 chars, lowercase alphanumeric and hyphens)
bash
curl -X POST https://api.computeportal.io/v1/storage/buckets \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "my-uploads" }'

Response 200

json
{
  "bucket": "usr123-my-uploads",
  "endpoint": "https://s3-cli.computeportal.io",
  "access_key": "AKIAIOSFODNN7EXAMPLE",
  "secret_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "region": "us-east-1"
}

Use these credentials with any S3-compatible client (AWS SDK, rclone, s3cmd).

StatusCondition
422Name format invalid

GET /v1/storage/buckets ​

List all buckets in your account.

Response 200

json
[
  {
    "id": "bkt_abc123",
    "bucket": "usr123-my-uploads",
    "endpoint": "https://s3-cli.computeportal.io",
    "access_key": "AKIAIOSFODNN7EXAMPLE",
    "region": "us-east-1",
    "created_at": "2026-07-01T10:00:00Z"
  }
]

GET /v1/storage/buckets/:name ​

Get credentials for a specific bucket.

Response 200 — same as the creation response, including secret_key.


DELETE /v1/storage/buckets/:name ​

Delete a bucket. By default, fails if the bucket contains objects.

Query Parameters

ParameterDescription
forcetrue to delete all objects first

Response 204 No Content

StatusCondition
409Bucket not empty (and force not set)

Config Maps ​

ConfigMaps store files that are mounted into containers at a specified path. The upload flow is two-step: get presigned S3 URLs, upload files directly from the client, then commit to create the Kubernetes ConfigMap.

POST /v1/configmaps/upload-token ​

Get presigned S3 PUT URLs for uploading ConfigMap files. Files must be uploaded directly to S3 before calling POST /v1/configmaps.

Request Body

FieldTypeRequiredDescription
namestringYesConfigMap alias name
filesstring[]YesFilenames to upload (max 20; no path separators)
bash
curl -X POST https://api.computeportal.io/v1/configmaps/upload-token \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "app-config", "files": ["config.json", "topology.json"] }'

Response 200

json
{
  "presigned_urls": {
    "config.json":   "https://s3-cli.computeportal.io/...",
    "topology.json": "https://s3-cli.computeportal.io/..."
  },
  "s3_prefix": "configmaps/<user-id>/app-config/"
}

Upload each file with an HTTP PUT to its presigned URL, then call POST /v1/configmaps with the same name, s3_prefix, and files list.

StatusCondition
422name missing, files empty, more than 20 files, or invalid filename

POST /v1/configmaps ​

Commit uploaded files as a Kubernetes ConfigMap. Fetches files from S3 (uploaded via presigned URLs) and creates or replaces the ConfigMap.

Request Body

FieldTypeRequiredDescription
namestringYesConfigMap alias name (same as used in upload-token)
s3_prefixstringYesFrom the upload-token response
filesstring[]YesFilenames (same list as upload-token)

Response 201

json
{
  "name": "app-config-usr12345",
  "alias": "app-config",
  "keys": ["config.json", "topology.json"]
}

name is the actual Kubernetes ConfigMap name (alias with a user-scoped suffix). Use alias when mounting with --mount-configmap.


GET /v1/configmaps ​

List all ConfigMaps in your account.

Response 200

json
[
  { "name": "app-config-usr12345", "alias": "app-config", "keys": ["config.json", "topology.json"] }
]

DELETE /v1/configmaps/:name ​

Delete a ConfigMap. Use the full Kubernetes name (from the name field in POST /v1/configmaps).

Response 204 No Content

StatusCondition
404ConfigMap not found

Environments ​

GET /v1/environments ​

List all environments.

Response 200

json
[
  { "name": "production", "created_at": "2026-06-01T00:00:00Z" },
  { "name": "staging", "created_at": "2026-06-15T00:00:00Z" }
]

POST /v1/environments ​

Create a new environment.

Request Body { "name": "staging" }

Response 200 { "name": "staging", "created_at": "..." }


DELETE /v1/environments/:name ​

Delete an environment.

Response 204 No Content


Sandboxes ​

Ephemeral GPU environments for short-lived experiments, fine-tuning runs, and interactive sessions.

GET /v1/sandboxes ​

List all sandboxes.

Response 200

json
[
  { "id": "sbx_abc123", "status": "running", "created_at": "2026-07-08T08:00:00Z", "expires_at": "2026-07-08T10:00:00Z" }
]

POST /v1/sandboxes ​

Create a new sandbox.

Response 200

json
{ "id": "sbx_abc123", "status": "starting" }

POST /v1/sandboxes/:id/exec ​

Run a command inside a sandbox.

Response 200 — exec session details (same shape as /v1/services/:name/exec).


DELETE /v1/sandboxes/:id ​

Destroy a sandbox immediately.

Response 204 No Content


Agent Account Invitations ​

Issue single-use tokens that provision new CP accounts — designed for AI agents and automated clients.

POST /v1/invitations ​

Issue an invitation token.

Request Body

FieldTypeRequiredDescription
labelstringYesHuman-readable label (e.g. "my-agent-prod")
ttl_hoursintegerNoToken lifetime in hours (default: 24)
bash
curl -X POST https://api.computeportal.io/v1/invitations \
  -H "Authorization: Bearer cp_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "label": "my-agent-prod", "ttl_hours": 48 }'

Response 200

json
{
  "id": "inv_abc123",
  "invitation_token": "cpinv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "label": "my-agent-prod",
  "expires_at": "2026-08-30T12:00:00Z"
}
StatusCondition
42910 active invitation limit reached

GET /v1/invitations ​

List all active invitation tokens issued by this account.

Response 200

json
[
  { "id": "inv_abc123", "label": "my-agent-prod", "expires_at": "2026-08-30T12:00:00Z", "used": false }
]

DELETE /v1/invitations/:id ​

Revoke a pending invitation.

Response 204 No Content


POST /v1/users/redeem (public) ​

Redeem an invitation token to create a new CP account. No Bearer token required.

Rate limit: 5 per 60 seconds per IP.

Request Body

FieldTypeRequiredDescription
invitation_tokenstringYesToken from POST /v1/invitations
bash
curl -X POST https://api.computeportal.io/v1/users/redeem \
  -H "Content-Type: application/json" \
  -d '{ "invitation_token": "cpinv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }'

Response 200

json
{
  "token": "cp_live_...",
  "user_id": "usr_abc123",
  "email": "agent-abc123@agents.computeportal.io"
}

The returned token is a full CP API token for the new account. Tokens are single-use — redeemed tokens cannot be used again.

StatusCondition
410Token not found, already used, or expired

Quota ​

GET /v1/billing/quota ​

Show compute quota usage for your active plan. Quota is scoped to the user — all API keys owned by the same account share this pool. The warning field is populated when either vcpu_pct or memory_pct crosses 80%.

Response 200

json
{
  "entitlement": {
    "vcpu": 10.0,
    "memory_gib": 20.0,
    "storage_gib": 100.0,
    "gpu_count": 2,
    "purchases": [
      {
        "sku": "gp-4-16-50",
        "category": "general-purpose",
        "vcpu": 4,
        "memory_gib": 16,
        "storage_gib": 50,
        "gpu": null,
        "status": "active"
      },
      {
        "sku": "gpu-16-80-h100",
        "category": "gpu",
        "vcpu": 16,
        "memory_gib": 80,
        "storage_gib": 500,
        "gpu": "NVIDIA H100 PCIe",
        "status": "active"
      }
    ]
  },
  "used": {
    "vcpu_millicores": 3500,
    "memory_mib": 7168,
    "gpu_count": 0,
    "vcpu_pct": 35,
    "memory_pct": 35
  },
  "available": {
    "vcpu_millicores": 6500,
    "memory_mib": 13312
  },
  "warning": null
}

entitlement is derived from the user's active rows in purchases (plan SKUs); used is reserved resources summed across live Deployments. Over-cap deploys return OVER_PAID_ENVELOPE with a hint pointing at computeportal.io/pricing.


Billing ​

GET /v1/billing/balance ​

Get current account balance and burn rate.

Response 200

json
{
  "balance_usd": 42.50,
  "credit_usd": 10.00,
  "status": "active",
  "alert_at_usd": 5.00,
  "burn_rate_usd_per_hour": 0.0125
}

GET /v1/payments/balance ​

Returns the subscription + per-API-key spend ceiling for the current auth context. Consumed by cpctl billing balance to render the subscription/api_key blocks.

Response 200

json
{
  "subscription": null,
  "api_key": {
    "label": "ci-production",
    "limit_cents": 50000,
    "spent_cents": 0,
    "remaining_cents": 50000
  },
  "has_credits": false
}
FieldMeaning
api_key.limit_centsSpend ceiling set at key-creation time (POST /v1/apikeys with limitCents). Zero means no ceiling.
api_key.spent_centsCumulative spend attributed to this key.
api_key.remaining_centsmax(0, limit_cents - spent_cents).

Status: the api_keys.{limit_cents, spent_cents} columns and the read API are live, but neither increment nor pre-flight enforcement is implemented yet — spent_cents is always 0 in production today. The field is agent-visible as a scaffold; the enforcement gate lives alongside the quota check in services.ts and is on the roadmap. For spend control today, rely on per-user quota (GET /v1/billing/quota), the x402 dynamic per-wallet cap, and wallet policy (daily/per-call limits in cp-wallet).


POST /v1/billing/topup ​

Initiate a crypto top-up and get a deposit address, or start a card top-up.

Request Body

FieldTypeRequiredDescription
amountnumberYesAmount in USD
networkstringFor cryptoBase, Ethereum, Solana, Tron, Cardano
tokenstringFor cryptoUSDC or USDT

Response (crypto) 200

json
{
  "payment_id": "topup_a1b2c3d4",
  "network": "Base",
  "token": "USDC",
  "amount_usd": 50.00,
  "deposit_address": "0x1a2b3c4d5e6f...",
  "expires_at": "2026-07-27T17:00:00Z"
}

The deposit address is valid for 30 minutes. After sending, confirm with POST /v1/billing/topup/verify.


POST /v1/billing/topup/verify ​

Submit a transaction ID to confirm and credit a crypto top-up.

Request Body

FieldTypeRequiredDescription
payment_idstringYesFrom the topup response
tx_idstringYesOn-chain transaction ID

Response 200

json
{
  "verified": true,
  "received": 50.00,
  "tx_id": "5xSol1ExampleTxHash123...",
  "new_balance": 92.50
}
StatusCondition
404payment_id not found
409Already settled
410Deposit address expired

GET /v1/billing/usage ​

Get usage totals for the current billing cycle.

Query Parameters

ParameterDescription
serviceFilter by service name
periodtoday, month, or YYYY-MM
group_byservice for per-service breakdown

Response 200

json
{
  "period": "2026-07",
  "total_usd": 128.40,
  "line_items": [
    { "service": "ml-inference", "resource": "cpu", "units": "720 vCPU-hours", "cost_usd": 72.00 },
    { "service": "ml-inference", "resource": "gpu", "units": "24 GPU-hours", "cost_usd": 48.00 },
    { "service": "app-db", "resource": "storage", "units": "50 GiB-months", "cost_usd": 8.40 }
  ]
}

POST /v1/billing/alert ​

Set a low-balance alert threshold.

Request Body { "threshold": 5.00 }

Response 200 { "ok": true, "alert_at_usd": 5.00 }


GET /v1/billing/history ​

List past usage snapshots (hourly billing history).

Query Parameters

ParameterDefaultDescription
days30Lookback window (1–90 days)

Response 200

json
[
  {
    "id": "snap_abc123",
    "cpu_cores": 3.5,
    "memory_gib": 7.0,
    "storage_gib": 50.0,
    "services": 4,
    "cost_usd": 0.42,
    "snapped_at": "2026-07-08T09:00:00Z"
  }
]

Machine Payments ​

Per-call, agent-native payments via a linked Stripe card. No pre-loaded balance required.

GET /v1/pay/status ​

Get the currently linked payment method.

Response 200

json
{
  "linked": true,
  "type": "card",
  "spend_limit": 1.00,
  "currency": "USD",
  "active": true,
  "last4": "6880",
  "exp": "05/2029",
  "cardholder_name": "CHOI YOON IL"
}

POST /v1/pay/setup ​

Start a card setup flow. Returns a Stripe-hosted URL.

Request Body

FieldTypeRequiredDescription
methodstringYescard
limitnumberNoPer-call spend limit in USD (default: 1.00)

Response 200

json
{
  "method": "card",
  "spend_limit": 1.00,
  "currency": "USD",
  "setup_url": "https://checkout.stripe.com/pay/cs_live_...",
  "session_id": "cps_abc123"
}

Poll GET /v1/pay/setup/poll?session_id=cps_abc123 to detect completion.


GET /v1/pay/setup/poll ​

Poll for completion of a card setup flow.

Query Parameters — session_id (required, from POST /v1/pay/setup)

Response 200

json
{ "status": "complete", "last4": "6880", "exp": "05/2029", "cardholder_name": "CHOI YOON IL", "spend_limit": 1.00 }

status values: pending, complete, expired


POST /v1/pay/authorize ​

Pre-authorize a per-call charge. The card is charged immediately.

Request Body

FieldTypeRequiredDescription
servicestringYesService name
amountnumberYesCharge amount in USD
actionstringNoAction type: deploy, start, domain-add (default: deploy)

Response 200

json
{
  "token": "mpp_tok_xxxxxxxxxxxxxxxxxxxx",
  "service": "my-api",
  "amount_usd": 0.10,
  "action": "deploy",
  "expires_at": "2026-07-27T12:05:00Z",
  "charged": true,
  "stripe_payment_intent_id": "pi_3Rxxxxxxxxxxxxxxxxxxxxxxxx"
}

Pass the token to POST /v1/services via the X-Payment-Token header (CLI: --pay-token). Token expires in 5 minutes.

StatusCondition
402No payment method linked, or amount exceeds spend limit

GET /v1/pay/history ​

List per-call payment charges.

Query Parameters — limit (1–100, default: 20)

Response 200

json
[
  {
    "id": "chg_abc123",
    "stripe_payment_intent_id": "pi_3Rxxxxxxxxxxxxxxxxxxxxxxxx",
    "service": "my-api",
    "action": "deploy",
    "amount_usd": 0.10,
    "status": "settled",
    "charged_at": "2026-07-27T07:01:10Z"
  }
]

GitHub Integration ​

GET /v1/github/status ​

Check whether a GitHub account is connected.

Response 200

json
{ "connected": true, "appName": "ComputePortal", "connectedAt": "2026-06-01T10:00:00Z" }

GET /v1/github/repos ​

List GitHub repos accessible via the connected account.

Response 200

json
[
  { "id": 123456, "fullName": "myorg/my-app", "private": false, "defaultBranch": "main" }
]

GET /v1/github/branches ​

List branches for a specific repo.

Query Parameters — repo=owner/name (required)

Response 200 — array of branch name strings.


POST /v1/github/pat ​

Store a GitHub Personal Access Token for pulling private ghcr.io images.

Request Body { "token": "ghp_xxxxxxxxxxxx" }

Response 200 { "ok": true }


Authentication (Token Lifecycle) ​

GET /v1/auth/me ​

Get the currently authenticated user.

Response 200

json
{
  "email": "alice@example.com",
  "user_id": "usr_abc123",
  "org": "default",
  "plan": "pro",
  "token_expires_at": 0
}

DELETE /v1/auth/token ​

Revoke the current token (logout). Evicts the token from the server-side cache.

Response 204 No Content


WebSocket Endpoints ​

WS /v1/services/:name/logs/ws ​

Stream live pod logs over WebSocket.

Query Parameters — tail (default: 100)

Auth: Pass token in the Authorization header during handshake.

Each frame is a UTF-8 text message containing one log line. Connection closes with 1000 on success, 1008 if unauthorized.


WS /v1/services/:name/exec/ws ​

Interactive shell via WebSocket.

Query Parameters — cmd (JSON-encoded string array, e.g. ["sh"])

Frame format:

  • Outbound (send): 0x00 prefix + stdin bytes
  • Inbound (receive): 0x01 prefix + stdout bytes, 0x02 prefix + stderr bytes

Infrastructure Reference ​

ComponentValue
API base URLhttps://api.computeportal.io
CNAME targetcpctl.app
Default service domain<service-name>-<user-slug>.cpctl.app
TLSLet's Encrypt (HTTP-01, provisioned asynchronously)
IngressTraefik on Kubernetes
OrchestratorKubernetes (K8s-native)
Object storage endpointhttps://s3-cli.computeportal.io
MCP endpointhttps://mcp.computeportal.io/mcp
API versionv1

Breaking changes will be introduced under a new version prefix (/v2/...). Non-breaking additions may be added to v1 at any time.


SDK and Tools ​

  • cpctl CLI — curl -fsSL https://install.computeportal.io | sh
  • MCP Server — AI assistant integration at https://mcp.computeportal.io/mcp (see MCP Server Reference)

GPU Compute Platform · Status