Describes the structure of API request bodies and responses. Each name maps to a body field in an API operation.
| Field | Type | Required | Description |
|---|
defaultBranch | string | Yes | The HEAD on disk is authoritative. Repo.defaultBranch is a copy of it, and if they diverge, this value is correct (0032_repos.sql). |
refs | object[] | Yes | — |
| Field | Type | Required | Description |
|---|
name | string | Yes | — |
path | string | Yes | path relative to the repository root |
kind | string | Yes | — |
size | integer | Yes | files only. directories are 0 |
oid | string | Yes | — |
| Field | Type | Required | Description |
|---|
ref | string | Yes | — |
commit | string | Yes | — |
path | string | Yes | — |
entries | RepoTreeEntry[] | Yes | — |
truncated | boolean | Yes | Truncated at 1000 entries |
| Field | Type | Required | Description |
|---|
ref | string | Yes | — |
commit | string | Yes | — |
path | string | Yes | — |
oid | string | Yes | — |
size | integer | Yes | — |
binary | boolean | Yes | Contains a NUL byte or is not UTF-8. Content is empty |
tooLarge | boolean | Yes | Exceeds 1 MiB; content is not included |
content | string | Yes | — |
| Field | Type | Required | Description |
|---|
oid | string | Yes | — |
message | string | Yes | — |
author | string | Yes | — |
email | string | Yes | — |
timestamp | string | Yes | — |
Since this becomes a URL path segment, it cannot contain .. or a slash. The source of
truth is the CHECK constraint in migration 0032 (keeping validation only in the
application would let admin scripts bypass it); what is here is a copy of that. It
cannot start with a dot. It is broader than the project name (a hostname label) —
uppercase letters, dots, and underscores are allowed.
Type: string
Defaulting to private is intentional — if the default were open, the direction of a
mistake would be a leak. public means anonymous clone is open (§5.1).
Type: string
A token inherits the user's permissions but can only lower them (§5.2). There is no
arithmetic in the raising direction.
Type: string
| Field | Type | Required | Description |
|---|
repoId | string | Yes | — |
orgSlug | Slug | Yes | — |
project | Slug | Yes | The project this repository belongs to (§19). The name is unique only within it. |
name | RepoName | Yes | — |
visibility | RepoVisibility | Yes | — |
defaultBranch | string | Yes | The HEAD on disk is the source of truth, and this value is a copy of it (0032_repos.sql). If they diverge, disk wins — this is pushed back to node-git's maintenance. |
sizeBytes | integer | Yes | An approximation used for quota enforcement. The exact value lives on disk (§8.2). |
lastPushedAt | string | No | — |
createdAt | string | Yes | — |
cloneUrl | string | No | https://<git-domain>/<org>/<project>/<repo>.git. Built by the CP — if the CLI assembled it, the local shape would leak into production (docs/dogfood-friction.md ⑤). Empty string if --git-domain is empty. |
sshUrl | string | No | git@<git-ssh-host>:<org>/<project>/<repo>.git. The name matches cloneUrl (§18). Empty string if --git-ssh-host is empty. |
| Field | Type | Required | Description |
|---|
ref | string | Yes | — |
old | string | No | Null means create. It is the only clue for recovering commits lost to a force-push. |
new | string | No | Null means delete |
forced | boolean | Yes | Was a non-fast-forward push. Since the MVP has no protected branches, it is recorded rather than blocked (§5.1). |
actorUser | string | No | — |
createdAt | string | Yes | — |
| Field | Type | Required | Description |
|---|
keyId | string | Yes | — |
name | string | Yes | — |
keyType | string | Yes | — |
fingerprint | string | Yes | Globally UNIQUE, because the key is the identity (§5.3). |
createdAt | string | No | — |
lastUsedAt | string | No | — |
| Field | Type | Required | Description |
|---|
tokenId | string | Yes | Carried in plaintext within the token string (runlot_pat_<id>_<secret>). That is why the lookup is a single index hit and the comparison runs in constant time (§5.2). |
name | string | Yes | — |
scope | TokenScope | Yes | — |
createdAt | string | Yes | — |
expiresAt | string | No | — |
lastUsedAt | string | No | — |
revokedAt | string | No | — |
| Field | Type | Required | Description |
|---|
tokenId | string | Yes | — |
name | string | Yes | — |
scope | TokenScope | Yes | — |
token | string | Yes | Plaintext. Carried in this response only once. The server stores only the SHA-256 hash, so it cannot be recreated. |
expiresAt | string | No | — |
Becomes a single label of the hostname, so it follows DNS rules. Since
the project name goes directly into <name>.lvh.me, not enforcing this
here would let you create a project that exists but is unreachable.
Type: string
| Field | Type | Required | Description |
|---|
error | object | Yes | — |
| Field | Type | Required | Description |
|---|
token | string | Yes | — |
expiresAt | string | No | — |
user | User | Yes | — |
| Field | Type | Required | Description |
|---|
userId | string | Yes | — |
provider | string | Yes | — |
subject | string | Yes | — |
email | object | No | — |
name | object | No | — |
| Field | Type | Required | Description |
|---|
orgId | string | Yes | — |
slug | Slug | Yes | — |
name | string | Yes | — |
personal | boolean | Yes | — |
Type: Org & object
| Field | Type | Required | Description |
|---|
id | integer | Yes | — |
kind | string | Yes | The kind of event (docs/limits.md §3.1). This is a value for machines to branch on; the human-readable sentence is produced separately by the dashboard and the email templates. |
projectName | string | No | If this is a project-related notification, the name of that project. Absent for org-level notifications (project_limit) |
key | string | Yes | A deduplication key per kind. Only one is created per (kind, key) in 24 hours. |
detail | object | Yes | A few numbers that differ by kind. project_limit is {limit, count}, a usage alert is {bytes, capBytes}, and an operation alert is {opId, opKind, reason?}. |
createdAt | string | Yes | — |
sentAt | string | No | When the email was sent. If absent, it is either still queued or there was no inbound email. |
| Field | Type | Required | Description |
|---|
projectId | string | Yes | — |
orgSlug | Slug | Yes | — |
name | Slug | Yes | — |
hostnames | string[] | Yes | — |
urls | string[] | No | hostnames turned into reachable addresses. The scheme and port are deployment-specific (production https://…, local http://…:8787), so the server builds this — if the client assembled it, the local shape would leak into production. |
createdAt | string | Yes | — |
Type: Project & object
| Field | Type | Required | Description |
|---|
state | string | Yes | — |
epoch | integer | Yes | — |
homeNode | object | No | — |
suspended | boolean | Yes | Separate from the lifecycle state. A suspended project can still have state active, and lifting the suspension returns it to that state. |
Project home page (getProjectOverview).
| Field | Type | Required | Description |
|---|
live | OverviewDeployment | null | Yes | The deployment currently serving traffic. Null before the first deployment. |
deployments | OverviewDeployment[] | Yes | Recent deployments, newest first. Up to 5 — use listDeployments for all of them. |
deploymentCount | integer | Yes | Total number of deployments for this project. |
database | OverviewDatabase | Yes | — |
features | OverviewFeatures | Yes | — |
customHostnames | integer | Yes | The number of custom domain rows. Rows still being verified count too. |
repos | integer | Yes | Number of repositories in this project. |
notifications | Notification[] | Yes | This project's recent notifications, newest first. Up to 5. |
A deployment row from the overview. Has the same shape as Deployment (includes actor).
Type: Deployment
| Field | Type | Required | Description |
|---|
granted | boolean | Yes | whether it is turned on with "database": true |
lastBackupAt | string | No | Timestamp of the last generation. Absent if there is no generation. |
lastCheckedAt | string | No | Last heartbeat of the no-change skip (DatabaseGenerations.lastCheckedAt). If this value keeps moving even when the generation does not advance, the backup is alive. |
generations | integer | Yes | number of generations |
Whether a resource enabled by declaration has been granted (docs/provisioning.md). The onboarding "next steps" screen picks the ones not yet enabled this way.
| Field | Type | Required | Description |
|---|
database | boolean | Yes | — |
storage | boolean | Yes | — |
email | boolean | Yes | — |
auth | boolean | Yes | — |
| Field | Type | Required | Description |
|---|
deploymentId | string | Yes | — |
version | integer | Yes | — |
artifactSha | string | Yes | SHA-256 (hex) of the deployment bundle tarball |
createdAt | string | Yes | — |
createdBy | object | No | — |
live | boolean | Yes | — |
actor | string | No | Display name of the person who deployed (in order: name, then email, then provider subject). createdBy is a uuid, so the UI cannot turn it into a person. It appears only in lists and overviews, and is empty if there is no user row. |
manifest | Manifest | No | The manifest exactly as it passed validation. Absent for deployments before 0040. |
bundleBytes | integer | No | The bundle size after unpacking, not the compressed size. |
bundleFiles | integer | No | Number of entries in the bundle (files + directories). |
gitCommit | string | No | The HEAD hash sent by the CLI. For display only — the CP does not verify it. |
gitMessage | string | No | The first line of the commit message, up to 200 characters. |
gitBranch | string | No | — |
gitDirty | boolean | No | Whether the deployment shipped with uncommitted changes. If true, gitCommit is not exactly the code. |
| Field | Type | Required | Description |
|---|
host | string | Yes | — |
port | integer | Yes | — |
database | string | Yes | — |
sslmode | string | Yes | — |
| Field | Type | Required | Description |
|---|
user | string | Yes | — |
expiresAt | string | Yes | — |
createdAt | string | Yes | — |
| Field | Type | Required | Description |
|---|
user | string | Yes | — |
password | string | Yes | Included only in this response. |
expiresAt | string | Yes | — |
connect | ConnectInfo | Yes | Same format as DatabaseConnect.connect; user/password are the values of this token. |
| Field | Type | Required | Description |
|---|
host | string | Yes | The project's hostname. It is not a UUID because front decides placement by SNI (docs/actor-placement-routing.md §8). |
port | integer | Yes | The wire listener port on node-front. This value is not computed by the CP; the operator tells both front and the CP what it is — the default is defined by the DefaultFrontWirePort constant in internal/slots. |
database | string | Yes | This is the project UUID. It is authoritative because front overwrites startup's database with pin's UUID. |
user | string | Yes | Bootstrap role. Names do not collide because each database has its own. |
password | string | Yes | — |
sslmode | string | Yes | The disabled value never responds. front cuts off the SSLRequest at the rejection stage, so disable never connects in the first place. |
| Field | Type | Required | Description |
|---|
db | boolean | Yes | — |
keyVersion | integer | No | The version of this project's DEK (docs/phase5.md C2). Included only in the database creation response, and the field is absent entirely if it could not be secured. The reason it is not returned as 0 is that keyVersion=0 already means "plaintext generation". Marking a failed acquisition with this value could lead a user to mistakenly believe the backup is confirmed as plaintext. When absent, the backup scheduler secures it later. |
connect | ConnectInfo | Yes | — |
| Field | Type | Required | Description |
|---|
projectId | string | Yes | — |
epoch | integer | Yes | The incarnation that created the image. Even for the same project, a different epoch means a different database, so this value matters as much as the key. |
node | string | Yes | Node that took the image |
export | object | Yes | — |
| Field | Type | Required | Description |
|---|
projectId | string | Yes | — |
epoch | integer | Yes | The currently running incarnation. Rows in the list can belong to an older epoch. |
state | string | Yes | Placement status (active, cold, restoring, and so on) |
lastCheckedAt | string | No | Last heartbeat of the no-change skip. If empty, it has never been checked yet. This is the value that distinguishes a stalled generation from a dead backup, so it cannot be removed from the list (docs/actor-placement-routing.md §4.6-1). |
generations | GenerationRow[] | Yes | Newest first. An empty array is an array, not null |
A single backup generation. The field names are the same as GenerationRow in
internal/backupproto. The actor metadata, the generation the node reports to the CP,
and the records included in this API must use the same names, so that a change in one
place is not silently interpreted as 0 elsewhere.
| Field | Type | Required | Description |
|---|
epoch | integer | Yes | — |
seq | integer | Yes | Increases monotonically within an epoch. Restore identifies a generation by (epoch, seq). |
key | string | Yes | Offsite key (gen/<project>/<epoch>/<stamp>-<seq>.feather) |
bytes | integer | Yes | The size of the object stored offsite. An encrypted generation is larger than the plaintext, and the admission reservation is also calculated based on this value. |
sha256 | string | Yes | — |
plainBytes | integer | No | The size of the plaintext (feather image) inside the encrypted data. For plaintext generations, this equals bytes |
keyVersion | integer | No | The DEK version used for encryption. 0 means plaintext (a generation created without a DEK) |
txnId | integer | No | Commit number that the image contains. 0 means unknown. |
kind | string | No | Why the generation was created. Not used for decisions |
createdAt | string | Yes | — |
node | string | No | The node that captured the generation |
latest | boolean | Yes | The generation that gen/<project>/latest.json points to. It is conditionally monotonic, so a later generation from an old epoch cannot claim this marker (docs/phase5.md A1). |
final | boolean | Yes | A safety copy taken right before eviction, deletion, or restore. It lives in the same table as regular generations, but pruning does not count it. |
| Field | Type | Required | Description |
|---|
epoch | integer | Yes | — |
seq | integer | Yes | — |
| Field | Type | Required | Description |
|---|
confirm | string | Yes | Must match the project name exactly. The CLI's prompt alone is not enough — a script with --yes running in the wrong directory never shows the prompt at all. |
This response means the multi-step operation was queued (202). It does not mean it
finished — check progress at …/database/operations/{opId}.
| Field | Type | Required | Description |
|---|
projectId | string | Yes | — |
opId | string | Yes | — |
kind | string | Yes | — |
epoch | integer | Yes | — |
node | string | No | — |
| Field | Type | Required | Description |
|---|
opId | string | Yes | — |
kind | string | Yes | — |
projectId | string | Yes | — |
phase | string | Yes | The step currently in progress (draining, sealing, committing, and so on). The CLI prints a line only when this value changes — printing on every poll would show the polling interval, not progress. |
attempt | integer | No | — |
deferrals | integer | No | — |
owner | string | No | — |
terminalCode | string | No | The reason it ended. aborted means a person stopped it, and any other value is a failure. Empty with done means success. |
failedAt | string | No | — |
error | string | No | — |
done | boolean | Yes | Does not proceed further. terminalCode distinguishes success, failure, and abort |
retryAfterSeconds | integer | No | Seconds remaining until the next attempt. 0 means it is ready to process now. Once backoff applies, "in progress" can refer to two states, so this value distinguishes them. |
The runlot.json inside the bundle. It does not travel in the HTTP body, but it is part
of the contract, so it is included here — the CLI writes it, cp-public reads it, and the
config generator consumes it.
| Field | Type | Required | Description |
|---|
main | object | No | The worker entry point's path within the bundle. If absent, this is a pure static site (the same model as Workers Static Assets). |
assets | object | No | Path to the static assets directory within the bundle |
compatibilityDate | string | No | — |
notFound | string | No | The answer for a path that is not in the assets. With spa, a path without an extension is served /index.html (client-side routing). The default is 404. A path with an extension is still a 404 under spa — a missing bundle must not become a silent 200. |
compatibilityFlags | string[] | No | The workerd compatibility flags to enable for the worker. Only flags on the allow list (internal/bundle.AllowedCompatibilityFlags) are accepted; the deployment is rejected otherwise — a single unsafe_module lets user code take over the process. Do not include experimental; the generator adds it on its own. nodejs_compat: for libraries (TypeORM, Sequelize, Knex) that require Node built-in modules (node:events, node:util, …). The CLI adds this when the bundle imports a Node built-in. |
modules | ManifestModule[] | No | A workerd module loaded separately next to the entry point. Currently only wasm — since workerd blocks compiling WASM inside the bundle (for example, Prisma 7's query compiler), WASM can only enter this way. name is exactly the specifier the worker code imports, and path is the file inside the bundle. The CLI writes one entry per .wasm import left by the bundle. |
framework | string | No | A marker that the deploy tool bundled with a framework adapter (docs/nextjs.md §3). If next, the node also renders the two bindings that OpenNext looks for (ASSETS alias, WORKER_SELF_REFERENCE). Unknown values cause the deployment to be rejected. |
triggers | ManifestTriggers | No | — |
email | boolean | No | Declares email sending and receiving (docs/email.md). When true, the worker receives env.email, and export default { email() } becomes the receiving handler. This only makes sense if main is present — static assets alone have no code to send or receive with. |
Scheduled execution (docs/cron.md). It ships in the bundle exactly as the user wrote it
in runlot.json — it matches the shape of wrangler's triggers because people
migrating over copy those three lines as is.
The only trigger known today is cron, but it is accepted as an object. If a queue or
email trigger is added later, the string the user writes must not have to change.
| Field | Type | Required | Description |
|---|
crons | string[] | No | A 5-field cron expression (minute hour day month weekday), in UTC. It has 1-minute granularity and allows up to 5 per project (bundle.MaxCronsPerProject); the org-wide limit is set by the plan (plan.CronLimitFor). The expression is actually parsed at deploy time — invalid syntax or an expression that never fires in the future (0 0 30 2 *) returns 400. Accepting anything that merely looks like a cron expression would let the deployment succeed while nothing ever runs. If triggers is set, main must also be set: a deployment with only static assets uses the generator's entry point, which has no scheduled handler. |
| Field | Type | Required | Description |
|---|
name | string | Yes | The specifier the worker imports. Alphanumeric and ._-, slash-separated, does not start with .. worker cannot be used because it is the entry module. |
type | string | Yes | — |
path | string | Yes | File path inside the bundle |
This is a containment relationship — anything a viewer can do, a member can also do, and
anything a member can do, an admin can also do (cp/internal/public/public.go roleRank).
Type: string
| Field | Type | Required | Description |
|---|
userId | string | Yes | — |
provider | string | Yes | — |
subject | string | Yes | — |
email | object | No | — |
name | object | No | — |
role | Role | Yes | — |
addedAt | string | Yes | — |
One kind of thing the org still holds (the org_not_empty details).
| Field | Type | Required | Description |
|---|
kind | string | Yes | — |
count | integer | Yes | — |
names | string[] | No | Only the first few. Listing all of them would make the error message overflow the screen. |
expired is the result of a time comparison, not a column — an invite isn't
marked expired, its expiry time has simply passed.
Type: string
No token. The plaintext token only goes in the email, and cp-public can't
even read that column (the column-level GRANT from migration 0043).
| Field | Type | Required | Description |
|---|
inviteId | string | Yes | — |
email | string | Yes | — |
role | Role | Yes | — |
state | InviteState | Yes | — |
invitedBy | string | No | The inviter's name or email. |
createdAt | string | Yes | — |
expiresAt | string | Yes | — |
sentAt | string | No | If empty, the send is still queued (sending is a cp-core cycle). |
sendError | string | No | The reason the last send failed. This can remain set even after a success. |
The invite as seen by whoever holds the token (answered without authentication).
| Field | Type | Required | Description |
|---|
orgSlug | string | Yes | — |
orgName | string | Yes | — |
role | Role | Yes | — |
email | string | Yes | The address the invite was sent to. It can differ from the address of the account that accepts it, and the UI should say so. |
invitedBy | string | No | — |
state | InviteState | Yes | — |
expiresAt | string | Yes | — |
Becomes the worker's env.<NAME>. DB and ASSETS (the uppercase counterparts of the
env.db and env.assets bindings) and the RUNLOT_ prefix are reserved — the first
two are already bindings, and the latter is the namespace Node uses for the process
environment (docs/phase6.md B4).
Type: string
| Field | Type | Required | Description |
|---|
name | SecretName | Yes | — |
version | integer | Yes | Increases by 1 on each PUT. The node determines replacement by the digest of the name and version |
updatedAt | string | Yes | — |
| Field | Type | Required | Description |
|---|
secrets | SecretMeta[] | Yes | — |
A single line of the log stream. Distinguished by type — line is an output line,
ping is a 15-second heartbeat, and end is the reason the server ended it.
| Field | Type | Required | Description |
|---|
type | string | Yes | — |
ts | string | No | The time the node received the line (line) |
epoch | integer | No | — |
generation | integer | No | The process generation within the node. Increments on replacement. |
stream | string | No | runlot is a line written by the node, not the worker — events such as startup, exit code, and the number of dropped lines. |
line | string | No | Without a trailing newline. Past 64 KiB it is truncated and truncated is set. |
truncated | boolean | No | — |
reason | string | No | Reason for end: process_exited, client_gone, node_shutdown |
| Field | Type | Required | Description |
|---|
projectId | string | Yes | — |
hours | integer | Yes | — |
rows | UsageRow[] | Yes | — |
totals | object | Yes | — |
dbBytes | integer | Yes | The most recently observed repository bytes. 0 if there is no observation. |
objBytes | integer | Yes | The most recently observed object bytes. This is a gauge, not a sum — adding it up across time buckets counts the same repository once per hour bucket (docs/storage.md §4.5). |
| Field | Type | Required | Description |
|---|
hour | string | Yes | start of the time window (UTC, on the hour) |
requests | integer | Yes | — |
cpuMs | integer | Yes | — |
dbBytes | integer | Yes | — |
objBytes | integer | Yes | gauge (last observed value) |
objOps | integer | Yes | cumulative (sum of deltas) |
mailOut | integer | Yes | cumulative (sum of deltas) |
mailIn | integer | Yes | cumulative (sum of deltas) |
카탈로그 한 줄 (docs/ai.md §2.3).
| Field | Type | Required | Description |
|---|
id | string | Yes | env.ai.run() 에 적는 문자열. 프론티어는 <provider>/<model>, 가성비 모델은 @runlot/<model> |
lane | string | Yes | — |
display | string | Yes | — |
vendor | string | Yes | 모델을 만든 곳 (Anthropic·Google·Meta…) |
provider | string | No | 우리가 부르는 곳. 프론티어에서만 실린다 — 가성비 모델은 우리가 갈아끼우는 값이라 계약이 아니다. |
contextTokens | integer | Yes | 모델 자체의 컨텍스트 |
maxInputTokens | integer | Yes | Runlot 이 이 모델에 허용하는 입력 상한. 대부분 contextTokens 와 같고, GPT-5.6 Terra 는 272,000 — 그 뒤는 요청 전체가 장문 단가로 바뀌므로 표준 단가 하나만 광고하는 카탈로그가 그 앞에서 자른다. |
maxOutputTokens | integer | Yes | — |
inPerMTok | integer | Yes | 100 만 입력 토큰당 마이크로센트 |
outPerMTok | integer | Yes | — |
cachedInPerMTok | integer | No | 캐시된 입력의 단가. 없으면 캐시 할인이 없는 모델이고 캐시된 토큰도 정가다 |
listPrice | boolean | Yes | 참이면 프로바이더 정가 그대로다 (마진 0). 화면이 그 사실을 말하는 것이 이 제품의 문장 하나이고, 말하지 않으면 사용자는 마크업을 가정한다. |
| Field | Type | Required | Description |
|---|
granted | boolean | Yes | — |
perMinute | integer | Yes | 프로젝트마다 분당 호출 상한 |
concurrent | integer | Yes | 프로젝트마다 동시 호출 상한 |
maxOutputTokens | integer | No | 0 이면 모델이 정한다 |
maxInputTokens | integer | No | 0 이면 모델이 정한다 |
creditMicroCents | integer | Yes | org 잔액 (음수 가능) |
freeDailyMicroCents | integer | No | 가성비 모델에만 주는 하루치 |
calls24h | integer | No | — |
tokensIn24h | integer | No | — |
tokensOut24h | integer | No | — |
spentMicroCents24h | integer | No | 최근 24 시간 원장의 합 (양수 = 쓴 금액). usage_hourly 에서는 못 만드는 수다 |
| Field | Type | Required | Description |
|---|
belowMicroCents | integer | Yes | — |
microCents | integer | Yes | — |
| Field | Type | Required | Description |
|---|
enabled | boolean | Yes | 거짓이면 이 배치에 크레딧 상품이 없다 — 충전 끝점은 501 |
sandbox | boolean | Yes | — |
microCents | integer | Yes | 잔액 (음수 가능) |
lowMicroCents | integer | No | — |
freeDailyMicroCents | integer | No | — |
tiers | integer[] | Yes | 충전 단위. 자동 충전이 같은 목록을 쓴다 |
hasPaymentMethod | boolean | Yes | 거짓이면 자동 충전을 못 켠다 — 첫 충전이 카드를 저장한다 |
autoTopup | AIAutoTopup | No | — |
autoTopupsToday | integer | Yes | — |
autoTopupPerDay | integer | Yes | 이 수가 곧 하루 지출 천장이다 |
| Field | Type | Required | Description |
|---|
kind | string | Yes | — |
model | string | No | — |
project | string | No | — |
tokensIn | integer | No | — |
tokensOut | integer | No | — |
microCents | integer | Yes | call 은 음수 |
at | string | Yes | — |
The project's email status (docs/email.md §5).
| Field | Type | Required | Description |
|---|
granted | boolean | Yes | Whether it is on. The existence of the project_email row is the only representation (0038). |
address | string | Yes | The project's mail domain. It receives everything at *@<address>. |
from | string | Yes | Sender address. The From of mail sent by env.email.send. |
perHour | integer | Yes | Hourly send limit. Shares the same bucket as authentication email. |
sentLastHour | integer | Yes | number sent in the current time window |
receivedLastHour | integer | Yes | number received in the current time window |
sent24h | integer | Yes | number sent in the last 24 hours |
received24h | integer | Yes | number received in the last 24 hours |
mailDomain | string | Yes | The mail domain suffix for this batch. Email cannot be enabled if it is empty. |
One line of the email log (docs/email.md §6.1).
| Field | Type | Required | Description |
|---|
mailId | string | Yes | — |
projectId | string | Yes | — |
direction | string | Yes | — |
at | string | Yes | — |
from | string | Yes | — |
to | string[] | Yes | — |
subject | string | Yes | — |
outcome | string | Yes | Outbound: sent → delivered, bounced, complained (relay feedback, docs/email.md §6.2), or failed / Inbound: ok, rejected, no_handler, exception |
detail | string | No | rejection sentence or error sentence — a human-readable line |
authResults | string | No | Authentication-Results of the received message |
sizeBytes | integer | Yes | — |
hasRaw | boolean | Yes | whether the original text is present |
| Field | Type | Required | Description |
|---|
entries | MailLogEntry[] | Yes | — |
cursor | string | No | next page. absent means the end |
keepDays | integer | Yes | number of days this org retains |
| Field | Type | Required | Description |
|---|
enabled | boolean | Yes | billing is configured for this deployment |
sandbox | boolean | Yes | Polar test environment — no real billing |
plan | string | Yes | — |
status | string | No | The Pro status. Canceled stays Pro until the period ends. Free has none |
periodEnd | string | No | end of the current billing period |
hasPortal | boolean | Yes | has billing history, so the portal can be opened |
projectCount | integer | Yes | Current project count. The UI explains when an org exceeds the limit after downgrading to Free — existing projects are not deleted; only new creation is blocked |
limits | object | Yes | — |
The limits for one plan (docs/limits.md §1)
| Field | Type | Required | Description |
|---|
projects | integer | Yes | — |
storageBytes | integer | Yes | — |
backupKeep | integer | Yes | number of retained generations (= time) |
crons | integer | Yes | — |
mailPerHour | integer | Yes | — |
This is the state of the project's dedicated object storage (docs/storage.md §7).
Objects are not part of DB generations — runlot pg restore does not roll back
files (§4.6). Read-after-write is guaranteed, but list may lag.
| Field | Type | Required | Description |
|---|
granted | boolean | Yes | Indicates whether storage usage is provisioned. Since the existence of the usage record is the only representation of this (migration 0022), there is no state of "provisioned but turned off". |
prefix | string | Yes | The prefix for the actual object key. User keys are stored only under this, and node-agent adds the prefix. If user keys required a project ID, sending a different one would be enough to access another project's bucket (§4.4). If storage is not configured, this is an empty string. |
quotaBytes | integer | Yes | The quota (1 GiB on Free). Once you exceed it, only put and presign(PUT) are rejected — get, list, and delete remain allowed, since blocking deletes along with writes would leave a project stuck at the cap with no way out (§4.5). Without a grant, it is 0. |
usedBytes | integer | Yes | The last observed byte count. This lags by the node's reporting interval, so you can slightly exceed the quota in between (open item in §10). 0 if there is no observation. |
Deployment access policy (docs/access.md §3.1). Provides only non-secret state.
Passwords and bypass keys are stored only as hashes, and the read API does not return
values (docs/access.md §6, the rule for the secret surface).
A project that has never set a policy has mode: public with no updatedAt. The reason
for distinguishing the two is that the UI states "this deployment is public" as a status
statement (§7).
| Field | Type | Required | Description |
|---|
mode | string | Yes | public means anyone, org means member or higher in the org the project belongs to, and password means one shared password per project. There is no other mode, because these values are the basis for the authorization (A3) policy — "anyone whose email domain is @acme.com" is not a boundary question. |
hasPassword | boolean | Yes | Whether a password is stored. The value itself is never returned. |
hasBypass | boolean | Yes | Indicates whether a bypass key for automation exists. The value is shown only once, at issuance. |
updatedAt | string | No | Omitted if never set |
A bypass key for automation. It is included only in this response. You cannot view
it again, and reissuing it immediately invalidates the previous key (docs/access.md
§3.6).
| Field | Type | Required | Description |
|---|
secret | string | Yes | Passed through as-is in the Runlot-Access-Bypass header |
The URL back to the protected deployment. A one-time code (60 seconds, single use,
scoped to that project) is included in the query string, and the front exchanges it,
sets a cookie, then issues a 302 back to the original URL without the query string. This
302 removes the code from the address bar (docs/access.md §3.4).
| Field | Type | Required | Description |
|---|
redirect | string | Yes | — |
A social login provider you can register a per-project OAuth app for.
Currently supported providers are GitHub, Google, and Kakao, and additional providers
are added based on demand (docs/auth.md §12 — SAML and OIDC arrive when enterprise
customers do).
Type: string
End-user auth settings (docs/auth.md §9). §12 explicitly does not support the email
template editor, MFA, or the device list.
| Field | Type | Required | Description |
|---|
allowSignup | boolean | Yes | Whether new signups are accepted. Turn it off to let only existing users log in — useful for invite-only apps or closing off a beta. |
requireVerifiedEmail | boolean | Yes | Whether email verification is required to log in. When enabled, unverified users cannot get a session from env.auth.user(request). |
sessionDays | integer | Yes | The lifetime of the session cookie, in days. It also affects the size of the session table. Since the project database is within the 150 MB cap (docs/auth.md §13), 10,000 active users each using three devices would produce 6 MB of session data. |
brand | AuthBrand | Yes | — |
Branding for the hosted login page (docs/auth.md §4). For finer-grained
branding, build your own page and call the JSON endpoint in §5. If all
values are empty strings, the project name and default design tokens are
used.
| Field | Type | Required | Description |
|---|
name | string | Yes | The name shown on the page. Falls back to the project name if empty |
logo | string | Yes | Logo image URL. Nothing is drawn if empty |
color | string | Yes | Accent color #rrggbb. Defaults to @runlot/design's default when empty. |
A single registered login provider. Secrets are not included. The client ID is a
public value also exposed to the browser, and the client secret is encrypted and never
included in any response (secret-surface rule, docs/access.md §6).
| Field | Type | Required | Description |
|---|
provider | AuthProvider | Yes | — |
clientId | string | Yes | The public id given by the provider |
updatedAt | string | Yes | When it was last registered or replaced |
The project's end-user auth status (docs/auth.md §9).
This API does not include the signup table. User data is stored in
the runlot_auth schema of the project's Postgres (§6), and this table is
handled together with project deletion, pg export, generational backup, and
restore. If CP served this table, the data lifecycle would be split.
| Field | Type | Required | Description |
|---|
granted | boolean | Yes | Indicates whether auth is configured for use. Since the existence of a usage-configuration record is the only representation, there is no "configured but disabled" state. When disabled, settings holds default values and providers is an empty array. |
settings | AuthSettings | Yes | — |
providers | AuthProviderMeta[] | Yes | Registered login providers. Sorted alphabetically by name, not by registration order |
hosts | string[] | Yes | Every hostname this project answers on. Each host needs its own callback URL registered in the login provider's console — https://<host>/__runlot/auth/callback/<provider>. Add the callback URL to the login provider every time you connect a host. That is why both the dashboard and the CLI hand you this list verbatim. On a host you miss, login fails on that host alone, and the reason is visible only in the provider's console. There are two default hostnames — <project>.<org>.<domain> and the older single-label name <project>--<org>.<domain>. A deploy registers both, so links already in the wild keep working, which is why both callback URLs are needed. |
A partial update of the settings. Only the fields you send change. Sending the
entire settings object for every single switch change can let two users editing the same
settings have a later save overwrite an earlier one.
| Field | Type | Required | Description |
|---|
settings | object | Yes | A subset of AuthSettings. An empty object changes nothing |
These are the login provider credentials. Replacement also happens through this same
PUT, so both values are required. If you send only the ID and omit the secret, the
response cannot confirm whether the previous secret remains.
| Field | Type | Required | Description |
|---|
clientId | string | Yes | The public id given by the provider |
clientSecret | string | Yes | Stored encrypted. It is never included in any response again. If you lose it, issue a new one from the login provider's console and PUT it again. |
A single hostname attached to the project. state progresses from
pending_dns (verifying ownership) → pending_tls (issuing the certificate) →
active (connected), and the failure state is error. Only records with
active are included in routing.
| Field | Type | Required | Description |
|---|
hostname | string | Yes | Name normalized to punycode and lowercase |
kind | string | Yes | default is the default name — this includes both <project>.<org>.<app-domain> and the legacy alias <project>--<org>.<app-domain>, which merges it into a single label. custom is a customer domain |
state | string | Yes | — |
lastError | string | No | The reason the last check failed. Users read this to fix their own DNS |
createdAt | string | Yes | — |
verify | HostnameVerify | No | — |
The two lines the customer needs to add to their own DNS. Getting either one right
proves ownership — CNAME also handles the connection, so it is the default guidance. CP
computes and returns the value (the fallback origin is a deployment setting, so if
clients hardcode it, they break the day it changes).
| Field | Type | Required | Description |
|---|
cname | HostnameRecord | Yes | — |
txt | HostnameRecord | Yes | — |
| Field | Type | Required | Description |
|---|
name | string | Yes | record name |
target | string | No | CNAME target |
value | string | No | TXT value |
| Field | Type | Required | Description |
|---|
hostnames | Hostname[] | Yes | — |
A zone in our authoritative DNS (docs/domains.md §4.3). serial is the rev — it
increases on every change, and once publishedSerial catches up, the nameservers are
answering with that content.
| Field | Type | Required | Description |
|---|
zoneId | string | Yes | — |
orgId | string | Yes | — |
name | string | Yes | example.com (lowercase punycode, no trailing dot) |
serial | integer | Yes | — |
publishedSerial | integer | Yes | The serial last written to the zone file. If it is smaller than serial, propagation is still in progress (usually 2 seconds) |
dnssec | string | Yes | — |
ds | string[] | Yes | The DS to submit to the parent. If we are the registrar, D3 submits it; otherwise the screen shows it. |
delegation | string | Yes | Result of a periodic check: whether the registrar's NS points to us |
delegationCheckedAt | string | No | — |
nameservers | string[] | Yes | The NS to enter at the registrar. A copy of the CP configuration, not the zone's value |
createdAt | string | Yes | — |
A single zone and all its records.
Type: Zone & object
A (name, type) value pair. If managedBy is set, the row was created by another layer
and is locked in the editor — system (zone's NS), hostname:<host> (app connection),
acme (certificate).
| Field | Type | Required | Description |
|---|
name | string | Yes | relative name. @ is the apex |
type | string | Yes | — |
ttl | integer | Yes | — |
records | string[] | Yes | — |
managedBy | string | No | — |
updatedAt | string | Yes | — |
| Field | Type | Required | Description |
|---|
zones | Zone[] | Yes | — |
Availability and value of a single name. No wholesale price is included —
cost is a fact about our operations, not the customer's (cp-core provides it
alongside, for operators).
| Field | Type | Required | Description |
|---|
name | string | Yes | — |
available | boolean | Yes | — |
premium | boolean | Yes | The name the registry prices separately. Registration and renewal values differ significantly |
priceCents | integer | Yes | first registration, 1 year (not KRW — cents) |
renewalCents | integer | Yes | renewal, 1 year |
The registrant's contact information. It belongs to the customer — ICANN
verification email goes to this address, and the domain is suspended if it is not
confirmed within 15 days. The UI must state this fact before registration
(docs/domains.md §5.5).
| Field | Type | Required | Description |
|---|
firstName | string | Yes | — |
lastName | string | Yes | — |
org | string | No | — |
email | string | Yes | — |
phone | string | Yes | — |
address1 | string | Yes | — |
address2 | string | No | — |
city | string | Yes | — |
state | string | No | — |
zip | string | Yes | — |
country | string | Yes | ISO 3166-1 alpha-2 |
A single domain. Expiration date, lock status, and nameservers are copies of the
registrar's source of truth and are synced by domain-sync (every 6 h)
(docs/domains.md §5.4).
| Field | Type | Required | Description |
|---|
name | string | Yes | — |
zoneId | string | No | Our zone. Always created on purchase or import |
registrar | string | Yes | external is a domain held in someone else's account — renewal and locking are not our responsibility |
state | string | Yes | This is derived from the expiration date: expiring starts 30 days before expiration, expired covers the 30-day grace period after expiration, redemption (redemption fee) covers the following 30 days, and released comes after that. transferred_out is when the registrar reports that it is no longer in our account. |
expiresAt | string | No | — |
autoRenew | boolean | Yes | This is the customer's decision. Auto-renewal on the registrar side is always on — we renew first and bill afterward (§5.4). |
locked | boolean | Yes | Transfer lock |
nameservers | string[] | Yes | — |
years | integer | Yes | — |
priceCents | integer | No | value the customer sent |
sagaStep | string | Yes | If not done, the purchase is still in progress (docs/domains.md §5.3) |
sagaError | string | No | reason it stopped. can be present even if the status is not failed |
createdAt | string | Yes | — |
A ledger row (append-only). kind is one of purchase, register_attempt,
register, register_reconciled, zone, ds, done, renew, lock,
unlock, auto_renew, auth_code, state, transfer_out, import,
error, and so on. Previous codes and secrets are never stored here.
| Field | Type | Required | Description |
|---|
eventId | integer | Yes | — |
kind | string | Yes | — |
detail | object | No | — |
at | string | Yes | — |
Type: Domain & object
| Field | Type | Required | Description |
|---|
domains | Domain[] | Yes | — |