Light Dark

HOT API

The Hot API provides programmatic access to manage projects, builds, runs, events, and more.

Base URL

https://api.hot.dev/v1

For local development:

http://localhost:4681/v1

Authentication

All API requests (except health checks) require a bearer token in the Authorization header:

curl https://api.hot.dev/v1/projects \
  -H "Authorization: Bearer <token>"

Hot supports three credential types — API keys, service keys, and sessions — all used the same way. All credentials are scoped to an environment, and resources are automatically filtered to that environment's context.

See the Authentication documentation for full details on credential types, the permissions model, and the permissions builder.

Permissions Model

API keys, service keys, and sessions share a granular permission system. Permissions are a JSON map of resource URNs to action arrays:

{
  "mcp:weather/get-forecast": ["execute"],
  "stream:*": ["read"],
  "event:user:*": ["create", "read"]
}

Resource URN format: type:path

  • type is the resource category
  • path is the resource identifier (* for wildcard)

Resource types and valid actions:

Resource TypeValid ActionsDescription
mcpexecuteMCP tool invocation (e.g., mcp:weather/get-forecast)
webhookexecuteWebhook endpoint access (e.g., webhook:payments/*)
streamreadStream subscription (e.g., stream:* or stream:<id>)
eventcreate, readEvent publishing and reading
runreadRun inspection
callcreate, readFunction call invocation
projectcreate, read, update, deleteProject management
buildcreate, read, executeBuild management and deployment
contextcreate, read, update, deleteContext variable management
keycreate, read, update, deleteAPI key management
sessioncreate, read, deleteSession management
envreadEnvironment information

The wildcard action * grants all valid actions for that resource type. The universal wildcard *:* with ["*"] grants unrestricted access.

Validation Rules:

Permissions are validated when creating or updating API keys, sessions, and service keys. The following rules apply:

RuleExample (rejected)Error
Resource must use type:path format"no-colon-here"Invalid resource
Resource key must not be empty""Invalid resource
Type must not be empty":path"Invalid resource
Path must not be empty"mcp:"Invalid resource
Bare * is not valid — use *:*"*"Invalid resource
* type only allows * path"*:foo"Invalid resource
Type must be alphanumeric/hyphens"mcp!:test"Invalid resource
Actions must not be empty{"mcp:*": []}Empty action list
Actions are lowercase only"Read", "CREATE"Invalid action
Only valid actions: create, read, update, delete, execute, *"destroy"Invalid action
Action must be valid for the resource type"mcp:*": ["create"]Action not valid for resource

Sessions and service keys must also be a subset of the parent API key's permissions — you cannot escalate permissions beyond what the issuing key allows.

Rate Limiting

API requests are rate limited per organization. Limits are based on your subscription plan:

PlanRequests per Second
Starter20 RPS
Pro100 RPS
Enterprise / Self-hostedUnlimited

When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header indicating how many seconds to wait before retrying.

Response Format

All responses use a consistent envelope format.

Success Response (Single Item)

{
  "data": {
    "project_id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "my-project"
  },
  "meta": {
    "request_id": "123e4567-e89b-12d3-a456-426614174000",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

Success Response (List)

{
  "data": [...],
  "pagination": {
    "total": 42,
    "limit": 20,
    "offset": 0,
    "has_more": true
  },
  "meta": {
    "request_id": "123e4567-e89b-12d3-a456-426614174000",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

Error Response

{
  "error": {
    "code": "not_found",
    "message": "Project not found",
    "request_id": "123e4567-e89b-12d3-a456-426614174000"
  }
}

Pagination

List endpoints support pagination via query parameters:

ParameterTypeDefaultDescription
limitint20Maximum results to return
offsetint0Number of results to skip

Endpoints

Health & Status

Get API Status

GET /status

Returns API server health information. No authentication required.

Response:

{
  "status": "ok",
  "service": "hot.dev api server",
  "version": "1.0.0",
  "git_sha": "abc1234",
  "start_time": "2026-01-15T10:00:00Z"
}

Projects

List Projects

GET /v1/projects

Query Parameters:

ParameterTypeDescription
limitintMax results (default: 20)
offsetintPagination offset

Response:

{
  "data": [
    {
      "project_id": "550e8400-e29b-41d4-a716-446655440000",
      "env_id": "660e8400-e29b-41d4-a716-446655440000",
      "name": "my-project",
      "active": true,
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T10:30:00Z"
    }
  ],
  "pagination": {...},
  "meta": {...}
}

Create Project

POST /v1/projects

Request Body:

{
  "name": "my-project"
}

Response: 201 Created with project data.

Get Project

GET /v1/projects/{project_id_or_slug}

Supports both UUID and project name (slug) in the URL.

Update Project

PATCH /v1/projects/{project_id_or_slug}

Request Body:

{
  "name": "new-project-name"
}

Delete Project

DELETE /v1/projects/{project_id_or_slug}

Response: 204 No Content


Builds

List Builds (All in Environment)

GET /v1/builds

Lists all builds across all projects in the environment.

Response includes project_name for each build.

List Builds (By Project)

GET /v1/projects/{project_id_or_slug}/builds

Get Build

GET /v1/projects/{project_id_or_slug}/builds/{build_id}

Response:

{
  "data": {
    "build_id": "550e8400-e29b-41d4-a716-446655440000",
    "project_id": "660e8400-e29b-41d4-a716-446655440000",
    "hash": "abc123def456",
    "size": 102400,
    "build_type": "bundle",
    "deployed": false,
    "active": true,
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:00Z",
    "storage_path": "s3://builds/...",
    "storage_backend": "s3"
  },
  "meta": {...}
}

Get Deployed Build

GET /v1/projects/{project_id_or_slug}/builds/deployed

Returns the currently deployed build for the project, or 404 if none.

Get Live Build

GET /v1/projects/{project_id_or_slug}/builds/live

Returns the live (development) build for the project, or 404 if none.

Upload Build

POST /v1/projects/{project_id_or_slug}/builds
Content-Type: multipart/form-data

Form Fields:

FieldRequiredDescription
fileYesThe build zip file
hashYesSHA hash of the build for validation
build_idNoOptional UUID; if provided, enables idempotent uploads

Examples:

curl

curl -X POST 'https://api.hot.dev/v1/projects/my-project/builds' \
  -H "Authorization: Bearer $HOT_API_KEY" \
  -F "file=@build.hot.zip" \
  -F "hash=$(sha256sum build.hot.zip | cut -d' ' -f1)"

JavaScript

const fs = require('fs');
const crypto = require('crypto');
const FormData = require('form-data');

const file = fs.readFileSync('build.hot.zip');
const hash = crypto.createHash('sha256').update(file).digest('hex');

const form = new FormData();
form.append('file', file, 'build.hot.zip');
form.append('hash', hash);

const response = await fetch(`${BASE_URL}/projects/my-project/builds`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${HOT_API_KEY}` },
  body: form
});

Python

import hashlib

with open('build.hot.zip', 'rb') as f:
    file_hash = hashlib.sha256(f.read()).hexdigest()

response = requests.post(
    f'{BASE_URL}/projects/my-project/builds',
    headers={'Authorization': f'Bearer {HOT_API_KEY}'},
    files={'file': open('build.hot.zip', 'rb')},
    data={'hash': file_hash}
)

Response: 201 Created

{
  "data": {
    "build_id": "550e8400-e29b-41d4-a716-446655440000",
    "project_id": "660e8400-e29b-41d4-a716-446655440000",
    "hash": "abc123def456",
    "size": 102400,
    "storage_path": "s3://builds/...",
    "storage_backend": "s3",
    "created_at": "2024-01-15T10:30:00Z"
  },
  "meta": {...}
}

If the build_id already exists, returns 200 OK with header X-Build-Exists: true.

Download Build

GET /v1/projects/{project_id_or_slug}/builds/{build_id}/download

Returns the build as a zip file with Content-Type: application/zip.

Deploy Build

POST /v1/projects/{project_id_or_slug}/builds/{build_id}/deploy

Marks the build as deployed and queues it for worker processing.


Context Variables (Secrets)

Context variables are encrypted secrets stored per-project. Values are encrypted at rest using AES-256-GCM.

Security: Values are never returned via API—only metadata (key, description, timestamps).

List Context Variables

GET /v1/projects/{project_id_or_slug}/context

Query Parameters:

ParameterTypeDescription
limitintMax results (default: 20)
offsetintPagination offset

Response:

{
  "data": [
    {
      "key": "DATABASE_URL",
      "description": "Production database connection",
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T10:30:00Z"
    }
  ],
  "pagination": {...},
  "meta": {...}
}

Create Context Variable

POST /v1/projects/{project_id_or_slug}/context

Request Body:

{
  "key": "DATABASE_URL",
  "value": "postgres://user:pass@host/db",
  "description": "Production database connection"
}

Response: 201 Created (value is not returned).

Update Context Variable

PUT /v1/projects/{project_id_or_slug}/context/{key}

Request Body:

{
  "value": "postgres://user:newpass@host/db",
  "description": "Updated description"
}

Delete Context Variable

DELETE /v1/projects/{project_id_or_slug}/context/{key}

Response: 204 No Content


Events

Publish Event

POST /v1/events

Publishes an event that can trigger event handlers.

event_type is an arbitrary string chosen by your application (for example user:created). Hot does not enforce a specific naming pattern, but :-separated names are the recommended convention.

For comparison:

  • Hot language send(...) uses type and data
  • HTTP API POST /v1/events uses event_type and event_data

Request Body:

{
  "event_type": "user.signup",
  "event_data": {
    "user_id": "123",
    "email": "alice@example.com"
  }
}

Response: 201 Created

{
  "data": {
    "event_id": "550e8400-e29b-41d4-a716-446655440000",
    "env_id": "660e8400-e29b-41d4-a716-446655440000",
    "stream_id": "770e8400-e29b-41d4-a716-446655440000",
    "event_type": "user.signup",
    "event_data": {...},
    "event_time": "2024-01-15T10:30:00Z",
    "created_at": "2024-01-15T10:30:00Z"
  },
  "meta": {...}
}

List Events

GET /v1/events

Query Parameters:

ParameterTypeDescription
limitintMax results (default: 20)
offsetintPagination offset

Get Event

GET /v1/events/{event_id}

Get Runs for Event

GET /v1/events/{event_id}/runs

Returns all runs triggered by this event.


Runs

List Runs

GET /v1/runs

Query Parameters:

ParameterTypeDescription
limitintMax results (default: 20)
offsetintPagination offset
statusstringFilter: running, succeeded, failed, cancelled
typestringFilter: call, event, schedule, run, eval, repl
time_rangestringISO 8601 duration: P7D, P30D, etc.

Response:

{
  "data": [
    {
      "run_id": "550e8400-e29b-41d4-a716-446655440000",
      "env_id": "660e8400-e29b-41d4-a716-446655440000",
      "stream_id": "770e8400-e29b-41d4-a716-446655440000",
      "build_id": "880e8400-e29b-41d4-a716-446655440000",
      "run_type": "event",
      "status": "succeeded",
      "start_time": "2024-01-15T10:30:00Z",
      "stop_time": "2024-01-15T10:30:45Z",
      "origin_run_id": null,
      "event_id": "990e8400-e29b-41d4-a716-446655440000",
      "result": {...},
      "project_id": "aa0e8400-e29b-41d4-a716-446655440000",
      "project_name": "my-project"
    }
  ],
  "pagination": {...},
  "meta": {...}
}

Get Run

GET /v1/runs/{run_id}

Get Run Statistics

GET /v1/runs/stats

Response:

{
  "data": {
    "total_runs": 1234,
    "running": 5,
    "succeeded": 1200,
    "failed": 25,
    "cancelled": 4
  },
  "meta": {...}
}

Event Handlers

Event handlers are registered in your Hot code and loaded when builds are uploaded.

List Event Handlers

GET /v1/projects/{project_id_or_slug}/event-handlers

Returns event handlers from the project's deployed build.

Response:

{
  "data": [
    {
      "event_handler_id": "550e8400-e29b-41d4-a716-446655440000",
      "build_id": "660e8400-e29b-41d4-a716-446655440000",
      "event_type": "user.signup",
      "ns": "::myapp::handlers",
      "var": "on-user-signup"
    }
  ],
  "pagination": {...},
  "meta": {...}
}

Schedules

Schedules are cron-based triggers defined in your Hot code.

List Schedules

GET /v1/projects/{project_id_or_slug}/schedules

Returns schedules from the project's deployed build.

Response:

{
  "data": [
    {
      "schedule_id": "550e8400-e29b-41d4-a716-446655440000",
      "build_id": "660e8400-e29b-41d4-a716-446655440000",
      "cron": "0 0 * * *",
      "ns": "::myapp::tasks",
      "var": "daily-cleanup"
    }
  ],
  "pagination": {...},
  "meta": {...}
}

Environment

Get Environment Info

GET /v1/env

Response:

{
  "data": {
    "env_id": "550e8400-e29b-41d4-a716-446655440000",
    "org_id": "660e8400-e29b-41d4-a716-446655440000",
    "name": "production",
    "active": true
  },
  "meta": {...}
}

Subscribe to Environment Events (SSE)

GET /v1/env/subscribe

Subscribe to real-time events for the entire environment via Server-Sent Events (SSE). This endpoint streams all run, event, and stream activity for the environment associated with your API key.

Response: text/event-stream

SSE Event Types:

EventDescription
run:startA new run has started
run:stopA run completed successfully
run:failA run failed
run:cancelA run was cancelled
event:createdA new event was created
event:handledAn event was handled
stream:createdA new stream was created

Example Events:

event: run:start
data: {"run_id":"550e8400-...","stream_id":"660e8400-...","run_type":"event"}

event: run:stop
data: {"run_id":"550e8400-...","stream_id":"660e8400-..."}

event: event:created
data: {"event_id":"770e8400-...","stream_id":"880e8400-...","event_type":"user.signup"}

Examples:

curl

curl -N 'https://api.hot.dev/v1/env/subscribe' \
  -H "Authorization: Bearer $HOT_API_KEY"

Note: -N disables buffering for real-time streaming output.

JavaScript

const eventSource = new EventSource('https://api.hot.dev/v1/env/subscribe', {
  headers: { 'Authorization': `Bearer ${HOT_API_KEY}` }
});

eventSource.addEventListener('run:stop', (e) => {
  const data = JSON.parse(e.data);
  console.log('Run stopped:', data.run_id);
});

eventSource.addEventListener('event:created', (e) => {
  const data = JSON.parse(e.data);
  console.log('Event created:', data.event_type);
});

Python

import requests
import json

response = requests.get(
    'https://api.hot.dev/v1/env/subscribe',
    headers={'Authorization': f'Bearer {HOT_API_KEY}'},
    stream=True
)

for line in response.iter_lines():
    if line:
        line = line.decode('utf-8')
        if line.startswith('data: '):
            event = json.loads(line[6:])
            print(f"Event: {event}")

Notes:

  • The stream automatically times out after 5 minutes. Reconnect to continue receiving events.
  • Events are scoped to the environment associated with your API key.
  • This endpoint requires pub/sub to be configured on the server.

Organization

Get Usage & Limits

GET /v1/org/usage

Returns current usage statistics, plan limits, and usage percentages for the organization.

Response:

{
  "data": {
    "org_id": "660e8400-e29b-41d4-a716-446655440000",
    "usage": {
      "runs_this_period": 1250,
      "file_storage_bytes": 52428800,
      "team_members": 5,
      "call_storage_bytes": 104857600,
      "call_count": 15000
    },
    "limits": {
      "runs_per_month": 10000,
      "storage_bytes": 1073741824,
      "team_members": 10,
      "call_retention_days": 30,
      "call_storage_bytes": 5368709120
    },
    "usage_percent": {
      "runs": 12.5,
      "file_storage": 4.9,
      "team_members": 50.0,
      "call_storage": 1.95,
      "has_warning": false
    },
    "plan": {
      "name": "Pro",
      "period_start": "2024-01-01T00:00:00Z",
      "period_end": "2024-02-01T00:00:00Z"
    }
  },
  "meta": {...}
}

Fields:

FieldDescription
usageCurrent usage in the billing period
limitsPlan limits (-1 = unlimited)
usage_percentUsage as percentage of limits (can exceed 100 if over limit)
usage_percent.has_warningTrue if any usage exceeds 90%
plan.period_startStart of current billing period
plan.period_endEnd of current billing period

For self-hosted/local deployments without a subscription, all limits are unlimited (-1).


Streams (Server-Sent Events)

Streams provide real-time updates for run execution via Server-Sent Events (SSE). A stream groups related events and runs together, allowing you to track the full lifecycle of an operation.

Subscribe to Stream

GET /v1/streams/{stream_id}/subscribe

Subscribe to an existing stream to receive real-time updates.

Query Parameters:

ParameterTypeDescription
projectstringOptional project filter

Response: text/event-stream

SSE Event Types:

EventDescription
run:startA new run has started
run:stopA run completed successfully
run:failA run failed
run:cancelA run was cancelled
stream:dataReal-time data from the run (e.g., AI tokens)
stream:completeStream timed out (5 minute default)

Example Event:

event: run:start
data: {"type":"run:start","run":{"run_id":"...","status":"running",...}}

event: stream:data
data: {"type":"stream:data","run_id":"...","data_type":"ai:delta","payload":{"text":"Hello"}}

event: run:stop
data: {"type":"run:stop","run":{"run_id":"...","status":"succeeded","result":"Hello world"}}

Subscribe with Event (Atomic)

POST /v1/streams/subscribe-with-event
Accept: text/event-stream

Recommended for streaming use cases. This endpoint atomically subscribes to a stream AND publishes an event in a single request, eliminating race conditions where events might be missed.

Request Body:

{
  "event_type": "chat:message",
  "event_data": {
    "message": "Hello, world!",
    "history": []
  },
  "stream_id": "optional-existing-stream-uuid"
}
FieldRequiredDescription
event_typeYesThe event type to publish
event_dataYesEvent payload (any JSON)
stream_idNoContinue an existing stream; if omitted, creates a new stream

Response: text/event-stream

The first event is always event:published confirming the event was queued:

event: event:published
data: {"type":"event:published","event_id":"...","stream_id":"...","event_type":"chat:message"}

event: run:start
data: {"type":"run:start","run":{...}}

event: stream:data
data: {"type":"stream:data","run_id":"...","data_type":"ai:delta","payload":{"text":"Hello"}}

event: run:stop
data: {"type":"run:stop","run":{...}}

Examples:

curl

curl -N -X POST 'https://api.hot.dev/v1/streams/subscribe-with-event' \
  -H "Authorization: Bearer $HOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"event_type": "chat:message", "event_data": {"message": "Hello!"}}'

Note: -N disables buffering for real-time streaming output.

JavaScript

const response = await fetch('https://api.hot.dev/v1/streams/subscribe-with-event', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${HOT_API_KEY}`,
    'Content-Type': 'application/json',
    'Accept': 'text/event-stream',
  },
  body: JSON.stringify({
    event_type: 'chat:message',
    event_data: { message: 'Hello!', history: [] }
  })
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const chunk = decoder.decode(value);
  // Parse SSE events from chunk
  for (const line of chunk.split('\n')) {
    if (line.startsWith('data: ')) {
      const event = JSON.parse(line.slice(6));
      console.log(event.type, event);
    }
  }
}

Python

import requests
import json

response = requests.post(
    'https://api.hot.dev/v1/streams/subscribe-with-event',
    headers={
        'Authorization': f'Bearer {HOT_API_KEY}',
        'Content-Type': 'application/json',
        'Accept': 'text/event-stream',
    },
    json={
        'event_type': 'chat:message',
        'event_data': {'message': 'Hello!', 'history': []}
    },
    stream=True  # Required for SSE
)

for line in response.iter_lines():
    if line:
        line = line.decode('utf-8')
        if line.startswith('data: '):
            event = json.loads(line[6:])
            print(event['type'], event)

Sessions

Sessions are short-lived, permission-scoped tokens for ephemeral access. Only API keys can create sessions.

Create Session

POST /v1/sessions

Request Body:

{
  "permissions": {
    "stream:*": ["read"],
    "event:user:*": ["create"]
  },
  "metadata": {
    "user_id": "end-user-123",
    "purpose": "stream-subscription"
  },
  "expires_in": 3600
}
FieldRequiredDescription
permissionsYesPermission map (resource URN → action array). Must be a subset of the parent API key's permissions.
metadataNoArbitrary JSON metadata (user ID, purpose, etc.)
expires_inNoTTL in seconds (default: 3600, max: 86400)

Response: 201 Created

{
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "token": "s_0193a7b212347def8abc123456789012_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
    "permissions": {"stream:*": ["read"], "event:user:*": ["create"]},
    "metadata": {"user_id": "end-user-123"},
    "expires_at": "2026-01-15T11:30:00Z",
    "created_at": "2026-01-15T10:30:00Z"
  },
  "meta": {...}
}

Important: The token field is only returned at creation time. Store it securely — it cannot be retrieved later.

Errors:

CodeStatusCause
forbidden403Non-API-key credential attempted to create a session
permission_escalation403Requested permissions exceed parent API key permissions
session_limit_exceeded429Maximum active sessions (1000) reached for this API key

List Sessions

GET /v1/sessions

Lists active (non-expired, non-revoked) sessions for the authenticated API key.

Query Parameters:

ParameterTypeDescription
limitintMax results (default: 20)
offsetintPagination offset

Revoke Session

DELETE /v1/sessions/{session_id}

Revokes a specific session. The session must belong to the authenticated API key.

Response: 204 No Content

Revoke All Sessions

DELETE /v1/sessions

Revokes all active sessions for the authenticated API key.

Response:

{
  "data": {
    "revoked_count": 5
  },
  "meta": {...}
}

Service Keys

Service keys are long-lived, permission-scoped credentials you issue to your customers or external systems for access to MCP tools, webhooks, and other API resources. Only API keys can create service keys.

Create Service Key

POST /v1/service-keys

Request Body:

{
  "name": "Acme Corp Production Key",
  "description": "MCP and stream access for Acme Corp",
  "permissions": {
    "mcp:weather/*": ["execute"],
    "stream:*": ["read"]
  },
  "metadata": {
    "customer_id": "acme-123"
  },
  "expires_in": null
}
FieldRequiredDescription
nameNoHuman-readable name
descriptionNoDescription of the key's purpose
permissionsYesPermission map. Must be a subset of the parent API key's permissions.
metadataNoArbitrary JSON metadata (encrypted at rest, available at runtime via req.auth.service-key.meta)
expires_inNoTTL in seconds (null or omitted = never expires)

Response: 201 Created

{
  "data": {
    "service_key_id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Corp Production Key",
    "description": "MCP and stream access for Acme Corp",
    "token": "0193a7b212347def8abc123456789012_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
    "permissions": {"mcp:weather/*": ["execute"], "stream:*": ["read"]},
    "metadata": {"customer_id": "acme-123"},
    "created_at": "2026-01-15T10:30:00Z"
  },
  "meta": {...}
}

Important: The token field is only returned at creation time. Store it securely — it cannot be retrieved later. Note that service key tokens have no hot_ prefix, making them suitable for white-label use.

Errors:

CodeStatusCause
forbidden403Non-API-key credential attempted to create a service key
permission_escalation403Requested permissions exceed parent API key permissions

List Service Keys

GET /v1/service-keys

Lists service keys for the authenticated API key.

Query Parameters:

ParameterTypeDescription
limitintMax results (default: 20)
offsetintPagination offset

Get Service Key

GET /v1/service-keys/{service_key_id}

Returns details for a specific service key. The key must belong to the authenticated API key.

Revoke Service Key

DELETE /v1/service-keys/{service_key_id}

Revokes a specific service key. The key must belong to the authenticated API key.

Response: 204 No Content

Revoke All Service Keys

DELETE /v1/service-keys

Revokes all active service keys for the authenticated API key.

Response:

{
  "data": {
    "revoked_count": 3
  },
  "meta": {...}
}

Custom Domains

Custom domains map your own domain names (e.g., mcp.example.com) to your Hot Dev environment. This feature requires a Pro or Scale subscription plan.

Register Domain

POST /v1/domains

Request Body:

{
  "domain": "mcp.example.com"
}

Response: 201 Created

{
  "data": {
    "domain_id": "550e8400-e29b-41d4-a716-446655440000",
    "env_id": "660e8400-e29b-41d4-a716-446655440000",
    "domain": "mcp.example.com",
    "status": "pending_validation",
    "acm_validation_cname_name": "_abc123.mcp.example.com",
    "acm_validation_cname_value": "_xyz789.acm-validations.aws",
    "cf_distribution_domain": null,
    "created_at": "2026-01-15T10:30:00Z"
  },
  "meta": {...}
}

After creating a domain, add the ACM validation CNAME record (using the acm_validation_cname_name and acm_validation_cname_value fields) to prove domain ownership. Once validated, the cf_distribution_domain field will be populated — add a domain CNAME pointing to the CloudFront distribution to start routing traffic.

Domain statuses: pending_validation, validated, provisioning, active, deleting.

Errors:

CodeStatusCause
plan_required403Custom domains require Pro or Scale plan
domain_limit_reached403Domain count limit reached for current plan
domain_exists409Domain is already registered

List Domains

GET /v1/domains

Lists all custom domains for the environment.

Get Domain

GET /v1/domains/{domain_id}

Verify Domain

POST /v1/domains/{domain_id}/verify

Checks the current provisioning status of the domain. If the ACM validation CNAME has propagated and the certificate is issued, the domain moves to validated status and CloudFront provisioning begins. If not yet validated, returns the required DNS records.

Pending domains are also checked automatically in the background, so you don't need to call this endpoint repeatedly.

Response (validated):

{
  "data": {
    "domain_id": "550e8400-e29b-41d4-a716-446655440000",
    "domain": "mcp.example.com",
    "status": "validated",
    "message": "Domain validated successfully — CloudFront provisioning in progress"
  },
  "meta": {...}
}

Response (pending):

{
  "data": {
    "domain_id": "550e8400-e29b-41d4-a716-446655440000",
    "domain": "mcp.example.com",
    "status": "pending_validation",
    "message": "Add a CNAME record: _abc123.mcp.example.com → _xyz789.acm-validations.aws"
  },
  "meta": {...}
}

Delete Domain

DELETE /v1/domains/{domain_id}

Removes a custom domain. The domain must belong to the authenticated environment. Deletion is asynchronous — the domain enters a deleting state while its CloudFront distribution and TLS certificate are cleaned up, then the record is removed.

Response: 204 No Content


Error Codes

CodeHTTP StatusDescription
unauthorized401Invalid, missing, expired, or revoked credential
forbidden403Credential lacks required permissions
permission_escalation403Requested permissions exceed parent credential permissions
plan_required403Feature requires a higher subscription plan
not_found404Resource not found
bad_request400Invalid request body or parameters
domain_exists409Custom domain is already registered
domain_limit_reached403Domain count limit reached for current plan
session_limit_exceeded429Maximum active sessions reached for this API key
rate_limit_exceeded429Too many requests (see Retry-After header)
internal_server_error500Server error

Code Examples

curl

# List projects
curl https://api.hot.dev/v1/projects \
  -H "Authorization: Bearer $HOT_API_KEY"

# Publish an event
curl -X POST https://api.hot.dev/v1/events \
  -H "Authorization: Bearer $HOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event_type": "user.signup", "event_data": {"user_id": "123"}}'

JavaScript

const HOT_API_KEY = process.env.HOT_API_KEY;
const BASE_URL = 'https://api.hot.dev/v1';

// List projects
const response = await fetch(`${BASE_URL}/projects`, {
  headers: { 'Authorization': `Bearer ${HOT_API_KEY}` }
});
const { data: projects } = await response.json();

// Publish an event
await fetch(`${BASE_URL}/events`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${HOT_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    event_type: 'user.signup',
    event_data: { user_id: '123', email: 'alice@example.com' }
  })
});

Python

import os
import requests

HOT_API_KEY = os.environ['HOT_API_KEY']
BASE_URL = 'https://api.hot.dev/v1'
headers = {
    'Authorization': f'Bearer {HOT_API_KEY}',
    'Content-Type': 'application/json'
}

# List projects
response = requests.get(f'{BASE_URL}/projects', headers=headers)
projects = response.json()['data']

# Publish an event
requests.post(
    f'{BASE_URL}/events',
    headers=headers,
    json={
        'event_type': 'user.signup',
        'event_data': {'user_id': '123', 'email': 'alice@example.com'}
    }
)