Describes the structure of API request bodies and responses. Each name links to the body entry of an API operation.
| Field | Type | Required | Description |
|---|
defaultBranch | string | Yes | The HEAD on disk is authoritative. Repo.defaultBranch is a copy of it, and when they disagree this value is the right one (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; 0 for directories |
oid | string | Yes | — |
| Field | Type | Required | Description |
|---|
ref | string | Yes | — |
commit | string | Yes | — |
path | string | Yes | — |
entries | RepoTreeEntry[] | Yes | — |
truncated | boolean | Yes | Cut off 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 | It contains NUL or is not UTF-8. The contents are empty |
tooLarge | boolean | Yes | Over 1 MiB, so the contents were not included |
content | string | Yes | — |
| Field | Type | Required | Description |
|---|
oid | string | Yes | — |
message | string | Yes | — |
author | string | Yes | — |
email | string | Yes | — |
timestamp | string | Yes | — |
It becomes a URL path segment, so it cannot contain .. or a slash. The authoritative rule is the CHECK in migration 0032 (if validation lives only in the application, maintenance scripts bypass it), and what is here is a copy of it. It cannot begin with a dot. It is wider than a project name (a hostname label) — uppercase letters, dots and underscores are allowed.
Type: string
Defaulting to private is deliberate — if the default were open, the direction of a mistake would be a leak. public opens anonymous clone (§5.1).
Type: string
A token inherits the user's permissions but can only lower them (§5.2). There is no arithmetic in the upward 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 inside it. |
name | RepoName | Yes | — |
visibility | RepoVisibility | Yes | — |
defaultBranch | string | Yes | The HEAD on disk is authoritative and this value is a copy of it (0032_repos.sql). When they disagree the disk wins — node-git's maintenance pushes it back. |
sizeBytes | integer | Yes | An approximation for quota enforcement. The exact value is on disk (§8.2). |
lastPushedAt | string | No | — |
createdAt | string | Yes | — |
cloneUrl | string | No | https://<git-domain>/<org>/<project>/<repo>.git. CP builds it — if the CLI assembled it, the local shape would be stamped onto production (docs/dogfood-friction.md ⑤). An empty string when --git-domain is empty. |
sshUrl | string | No | git@<git-ssh-host>:<org>/<project>/<repo>.git. The name is the same as cloneUrl (§18). An empty string when --git-ssh-host is empty. |
| Field | Type | Required | Description |
|---|
ref | string | Yes | — |
old | string | No | null means a creation. It is the only clue for getting back a commit lost to a force-push. |
new | string | No | null means a deletion |
forced | boolean | Yes | It was a non-fast-forward. The MVP has no protected branch, so it goes into the record instead of being forbidden (§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 | It is carried in the token string in plaintext (runlot_pat_<id>_<secret>). That is why the lookup is one index hit and the comparison is 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. It appears in this response only once. The server stores only the SHA-256 hash, so it cannot be produced again. |
expiresAt | string | No | — |
It becomes one label of a hostname, so it follows the DNS rules. The project name goes into <name>.lvh.me verbatim, so without this check you could create a project that can never be reached.
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). It is the value machines branch on; the sentence a person reads is composed separately by the dashboard and by the email template. |
projectName | string | No | The project name, if the notification is about a project. Absent for organization notifications (project_limit) |
key | string | Yes | The deduplication key per kind. Only one row is created per (kind, key) in 24 hours |
detail | object | Yes | A few numbers that differ per kind. project_limit carries {limit, count}, usage notifications carry {bytes, capBytes}, and operation notifications carry {opId, opKind, reason?}. |
createdAt | string | Yes | — |
sentAt | string | No | The time the email was sent. If absent, it is still queued or there was no address to receive it |
| Field | Type | Required | Description |
|---|
projectId | string | Yes | — |
orgSlug | Slug | Yes | — |
name | Slug | Yes | — |
hostnames | string[] | Yes | — |
urls | string[] | No | hostnames turned into openable addresses. The scheme and port depend on the deployment (https://… in production, http://…:8787 locally), so the server builds them — if the client assembled them, the local shape would end up printed in 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. |
The project home screen (getProjectOverview).
| Field | Type | Required | Description |
|---|
live | OverviewDeployment | null | Yes | The deploy serving right now. It is null before the first deploy. |
deployments | OverviewDeployment[] | Yes | Recent deploys, newest first. At most 5 — for all of them, listDeployments. |
deploymentCount | integer | Yes | The total number of deploys for this project. |
database | OverviewDatabase | Yes | — |
features | OverviewFeatures | Yes | — |
customHostnames | integer | Yes | The number of custom domain rows. Rows still being verified are counted too. |
repos | integer | Yes | The number of repositories in this project. |
notifications | Notification[] | Yes | Recent notifications for this project, newest first. At most 5. |
A deploy row in the overview. It has the same shape as Deployment (including actor).
Type: Deployment
| Field | Type | Required | Description |
|---|
granted | boolean | Yes | Whether it was turned on with "database": true |
lastBackupAt | string | No | The time of the last generation. Absent when there is no generation. |
lastCheckedAt | string | No | The last heartbeat of the no-change skip (DatabaseGenerations.lastCheckedAt). Even when the generation count does not grow, the backup is alive as long as this value moves. |
generations | integer | Yes | Number of generations |
Whether the resources turned on by declaration have been granted (docs/provisioning.md). The "next step" on the home screen uses this to pick what has not been turned on.
| 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 of the deploy bundle tarball (hex) |
createdAt | string | Yes | — |
createdBy | object | No | — |
live | boolean | Yes | — |
actor | string | No | The display name of the person who deployed (name → email → provider subject, in that order). createdBy is a uuid, so the screen cannot turn it into a person. It appears only in lists and the overview, and it is empty when there is no user row. |
manifest | Manifest | No | The manifest exactly as it passed validation. Deploys from before 0040 do not have it. |
bundleBytes | integer | No | The bundle size after unpacking, not the compressed size. |
bundleFiles | integer | No | The number of bundle entries (files + directories). |
gitCommit | string | No | The HEAD hash sent by the CLI. For display only — 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 deploy carried uncommitted changes. When true, gitCommit is not 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 in this response only. |
expiresAt | string | Yes | — |
connect | ConnectInfo | Yes | The same shape as DatabaseConnect.connect, where user/password are the values of this token. |
| Field | Type | Required | Description |
|---|
host | string | Yes | The hostname of the project. It is not a UUID because the front decides placement from SNI (docs/actor-placement-routing.md §8). |
port | integer | Yes | The wire listener port of node-front. It is not a value the CP computes; the operator tells both the front and the CP — the default is defined by the DefaultFrontWirePort constant in internal/slots. |
database | string | Yes | The project UUID. The front overwrites the database from startup with the UUID from the pin, so this is the source of truth. |
user | string | Yes | The bootstrap role. It is separate per database, so names do not collide |
password | string | Yes | — |
sslmode | string | Yes | Values that turn it off get no answer. The front cuts the connection at the SSLRequest rejection step, 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). It is included in the database-creation response only, and if the key could not be provisioned the field is absent altogether. It does not answer 0, because keyVersion=0 already means "plaintext generation". Marking a provisioning failure with that value would let a user believe a plaintext backup had been settled on. If it is absent, the backup scheduler provisions the key later. |
connect | ConnectInfo | Yes | — |
| Field | Type | Required | Description |
|---|
projectId | string | Yes | — |
epoch | integer | Yes | The incarnation that produced the image. Even within one project, a different epoch is a different database, so this value matters as much as the key. |
node | string | Yes | The node that took the image |
export | object | Yes | — |
| Field | Type | Required | Description |
|---|
projectId | string | Yes | — |
epoch | integer | Yes | The incarnation running now. Rows in the list may belong to older epochs |
state | string | Yes | Assignment state (active·cold·restoring…) |
lastCheckedAt | string | No | The last heartbeat of the no-change skip. If it is empty the check has never run. It is the value that separates "no new generations" from "the backup is dead", so it cannot be dropped from the list (docs/actor-placement-routing.md §4.6-1). |
generations | GenerationRow[] | Yes | Newest first. An empty list is an array, not null |
One 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 record carried by this API must use the same names, so that editing one of them does not make another silently read 0.
| Field | Type | Required | Description |
|---|
epoch | integer | Yes | — |
seq | integer | Yes | Monotonically increasing within an epoch. A restore names 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 plaintext, and the admission reservation is computed from this value as well. |
sha256 | string | Yes | — |
plainBytes | integer | No | The size of the plaintext (the feather image) inside the encrypted data. For a plaintext generation it equals bytes |
keyVersion | integer | No | The DEK version used for encryption. 0 means plaintext (a generation created without a DEK) |
txnId | integer | No | The commit number the image holds. 0 means unknown |
kind | string | No | Why the generation was created. Not used for decisions |
createdAt | string | Yes | — |
node | string | No | The node that took the generation |
latest | boolean | Yes | The generation gen/<project>/latest.json points at. It is conditionally monotonic, so a late generation from an older epoch cannot take this marker (docs/phase5.md A1). |
final | boolean | Yes | A safety copy taken right before an eviction, a delete or a restore. It lives in the same table as scheduled 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 prompt alone is not enough — a script that passes --yes and runs in the wrong directory never shows a prompt at all. |
The answer means the multi-step operation was queued (202). It does not mean it finished — follow the 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…). The CLI prints a line only when this value changes — printing on every poll would show the polling interval instead of the progress. |
attempt | integer | No | — |
deferrals | integer | No | — |
owner | string | No | — |
terminalCode | string | No | Why it ended. aborted means a person stopped it; any other value is a failure. Empty together with done means success. |
failedAt | string | No | — |
error | string | No | — |
done | boolean | Yes | It will not progress any further. terminalCode is what separates success, failure and abort |
retryAfterSeconds | integer | No | Seconds remaining until the next attempt. 0 means it is due now. Once a backoff is applied, "in progress" can mean two different states, so this value tells them apart. |
The runlot.json inside the bundle. It never travels as an HTTP body, but it is part of the contract, so it is documented here — the CLI writes it, cp-public reads it, and the config generator consumes it.
| Field | Type | Required | Description |
|---|
main | object | No | The path of the worker entrypoint inside the bundle. If it is absent, the deployment is a purely static site (the same model as Workers Static Assets). |
assets | object | No | The path of the static assets directory inside the bundle |
compatibilityDate | string | No | — |
notFound | string | No | The answer for a path that is not in the assets. With spa, extensionless paths are answered with /index.html (client-side routing). Defaults to 404. Paths that do have an extension still return 404 even under spa — a missed 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; anything else makes the deploy fail — unsafe_module alone hands the process to user code. experimental is added by the generator itself, so do not write it. nodejs_compat: for libraries that require node built-in modules (node:events, node:util, …) such as TypeORM, Sequelize and Knex. The CLI writes this flag when the bundle imports a node built-in. |
modules | ManifestModule[] | No | workerd modules shipped alongside the entrypoint. Today it is only wasm — workerd blocks WASM compilation inside the bundle, so WASM (such as the Prisma 7 query compiler) can enter only this way. name is exactly the specifier the worker code imports, and path is a file inside the bundle. The CLI writes one entry for each .wasm import the bundle left behind. |
framework | string | No | A marker that the deploy tool bundled the project through a framework adapter (docs/nextjs.md §3). With next, the node additionally renders the two bindings OpenNext looks for (the ASSETS alias and WORKER_SELF_REFERENCE). An unknown value makes the deploy fail. |
triggers | ManifestTriggers | No | — |
email | boolean | No | Declares email sending and receiving (docs/email.md). When true, the worker gets env.email and export default { email() } becomes the receive handler. It only means something with main — static assets alone have no code to send or receive with. |
Scheduled execution. Carried into the bundle exactly as written in runlot.json. It has the same shape as wrangler’s triggers because people migrating copy those three lines verbatim.
Cron is the only trigger we know today, but this is an object so that adding queues or email later does not change what users write.
| Field | Type | Required | Description |
|---|
crons | string[] | No | Five-field cron expressions (minute hour day-of-month month day-of-week), in UTC. One-minute granularity, up to 5 per project (bundle.MaxCronsPerProject); the per-organization cap comes from the plan (plan.CronLimitFor). Expressions are really parsed at deploy time — a malformed one, or one that will never fire (0 0 30 2 *), is a 400. Accepting them on shape alone would mean the deploy succeeds and nothing ever happens. triggers requires main: an assets-only deploy uses the generated entry point, which has no scheduled. |
| Field | Type | Required | Description |
|---|
name | string | Yes | The specifier the worker imports. Alphanumerics and ._-, separated by slashes, and it must not start with .. worker is the entry module, so it cannot be used. |
type | string | Yes | — |
path | string | Yes | The file path inside the bundle |
The roles are nested — whatever a viewer can do a member can do too, and whatever a member can do an admin can do too (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 | — |
expired 는 시각 비교의 결과이지 열이 아니다 — 초대는 만료되었다고 표시되지 않고, 만료 시각이 지났을 뿐이다.
Type: string
토큰이 없다. 평문 토큰은 메일에만 실리고, cp-public 은 그 열을 읽지도 못한다 (마이그레이션 0043 의 열 단위 GRANT).
| Field | Type | Required | Description |
|---|
inviteId | string | Yes | — |
email | string | Yes | — |
role | Role | Yes | — |
state | InviteState | Yes | — |
invitedBy | string | No | 부른 사람의 이름 또는 이메일. |
createdAt | string | Yes | — |
expiresAt | string | Yes | — |
sentAt | string | No | 비어 있으면 아직 발송 큐에 있다 (발송은 cp-core 의 회차다). |
sendError | string | No | 마지막 발송 실패 이유. 성공해도 남을 수 있다. |
토큰을 든 사람에게 보이는 초대 (인증 없이 답한다).
| Field | Type | Required | Description |
|---|
orgSlug | string | Yes | — |
orgName | string | Yes | — |
role | Role | Yes | — |
email | string | Yes | 초대를 보낸 주소다. 수락하는 계정의 주소와 다를 수 있고, 화면은 그 사실을 말해야 한다. |
invitedBy | string | No | — |
state | InviteState | Yes | — |
expiresAt | string | Yes | — |
Becomes env.<NAME> in the worker. DB·ASSETS (the uppercase counterparts of the env.db·env.assets bindings) and the RUNLOT_ prefix are reserved — the first two are already bindings, and the last is the namespace the node writes into the process environment (docs/phase6.md B4).
Type: string
| Field | Type | Required | Description |
|---|
name | SecretName | Yes | — |
version | integer | Yes | Increments by 1 on every PUT. The node decides whether to swap from the digest of the names and versions |
updatedAt | string | Yes | — |
| Field | Type | Required | Description |
|---|
secrets | SecretMeta[] | Yes | — |
One line of the log stream. type tells them apart — line is one line of output, ping is the 15-second heartbeat, and end is why the server ended the stream.
| 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 inside the node. It goes up when the process is replaced |
stream | string | No | runlot marks a line written by the node rather than by the worker — events such as startup, exit codes and the number of dropped lines. |
line | string | No | Without the trailing newline. Anything longer than 64 KiB is cut and truncated is set |
truncated | boolean | No | — |
reason | string | No | The 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 storage bytes. 0 if there is no observation |
objBytes | integer | Yes | The most recently observed object bytes. It is a gauge, not a sum — adding it up per hour counts the same storage once for every hour (docs/storage.md §4.5). |
| Field | Type | Required | Description |
|---|
hour | string | Yes | Start of the hour (UTC |
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) |
The project's email state (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. Receiving covers all of *@<address>. |
from | string | Yes | The sender address — the From of mail sent with env.email.send. |
perHour | integer | Yes | Hourly send limit. Shared with auth mail. |
sentLastHour | integer | Yes | Messages sent in the current hour |
receivedLastHour | integer | Yes | Messages received in the current hour |
sent24h | integer | Yes | Messages sent in the last 24 hours |
received24h | integer | Yes | Messages received in the last 24 hours |
mailDomain | string | Yes | This deployment's mail domain suffix. Empty means email cannot be turned on. |
One row of the mail 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 | The rejection or error sentence — one line for a person to read |
authResults | string | No | Authentication-Results of a received message |
sizeBytes | integer | Yes | — |
hasRaw | boolean | Yes | Whether the raw message exists |
| Field | Type | Required | Description |
|---|
entries | MailLogEntry[] | Yes | — |
cursor | string | No | The next page. Absent means the end |
keepDays | integer | Yes | The retention period in days for this org |
| Field | Type | Required | Description |
|---|
enabled | boolean | Yes | 이 배치에 결제가 설정돼 있다 |
sandbox | boolean | Yes | Polar 테스트 환경 — 실제 청구 없음 |
plan | string | Yes | — |
status | string | No | 프로의 상태. canceled 는 기간 끝까지 프로. free 는 없다 |
periodEnd | string | No | 현재 결제 기간의 끝 |
hasPortal | boolean | Yes | 결제 이력이 있어 포털을 열 수 있다 |
projectCount | integer | Yes | 지금 프로젝트 수. 무료로 내려간 뒤 한도를 넘긴 상태를 화면이 말한다 — 넘긴 것은 지우지 않고 새 생성만 막힌다 |
limits | object | Yes | — |
요금제 하나의 한도 (docs/limits.md §1)
| Field | Type | Required | Description |
|---|
projects | integer | Yes | — |
storageBytes | integer | Yes | — |
backupKeep | integer | Yes | 보존 세대 수 (= 시간) |
crons | integer | Yes | — |
mailPerHour | integer | Yes | — |
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 files back (§4.6). Read-after-write is guaranteed, but list can lag.
| Field | Type | Required | Description |
|---|
granted | boolean | Yes | Whether storage has been enabled. The existence of the enablement record is the only representation (migration 0022), so there is no "configured but off" state. |
prefix | string | Yes | The prefix of the real object keys. User keys are stored only under it, and it is the node-agent that prepends the prefix. If the user key had to carry the project ID, changing it in the request would be enough to reach another project's bucket (§4.4). Empty string when storage is not enabled. |
quotaBytes | integer | Yes | The cap (1 GiB on the free plan). Above it only put and presign(PUT) are rejected, while get·list·delete stay allowed — blocking deletes along with writes would leave a project trapped at its limit with no way out (§4.5). 0 when nothing is granted. |
usedBytes | integer | Yes | The last observed bytes. It lags by the node's reporting interval, so the quota can be exceeded slightly in between (an open item in §10). 0 if there is no observation. |
The deployment access policy (docs/access.md §3.1). It exposes state only, never secret values. The password and the bypass key are stored as hashes only, and the read API does not return the values (docs/access.md §6, the rule for secret surfaces).
A project that has never set a policy has mode: public and no updatedAt. The two are kept apart because the UI states "this deployment is public" as a status sentence (§7).
| Field | Type | Required | Description |
|---|
mode | string | Yes | public is anyone, org is member or above in the org the project belongs to, and password is one shared password per project. There are no other modes because these values are the basis of the authorization (A3) policy — "anyone whose email domain is @acme.com" is not a question about the boundary. |
hasPassword | boolean | Yes | Whether a password is stored. The value is never returned |
hasBypass | boolean | Yes | Whether a bypass key for automation exists. The value is shown only once, when it is issued |
updatedAt | string | No | Omitted if it has never been set |
The bypass key for automation. Included in this response only. It cannot be viewed again, and reissuing invalidates the previous key immediately (docs/access.md §3.6).
| Field | Type | Required | Description |
|---|
secret | string | Yes | Send it as-is in the Runlot-Access-Bypass header |
The URL that leads back to the protected deployment. A one-time code (60 seconds, single use, scoped to that project) is carried in the query; the front exchanges it, sets the cookie, and then 302s back to the original URL without the query. That 302 is what removes the code from the address bar (docs/access.md §3.4).
| Field | Type | Required | Description |
|---|
redirect | string | Yes | — |
The social login providers for which a per-project OAuth app can be registered. The providers supported today are GitHub, Google and Kakao; more will be added as demand appears (docs/auth.md §12 — SAML and OIDC when enterprise customers arrive).
Type: string
End-user auth settings (docs/auth.md §9). The email template editor, MFA and the device list are explicitly out of scope in §12.
| Field | Type | Required | Description |
|---|
allowSignup | boolean | Yes | Whether new sign-ups are accepted. When it is off, only people who already exist can log in — used for invite-only apps or for closing a beta. |
requireVerifiedEmail | boolean | Yes | Whether email verification is required before login. When it is on, an unverified user gets no 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. The project DB lives within the 150 MB cap (docs/auth.md §13), and 10,000 active users on three devices each amount to 6 MB of session data. |
brand | AuthBrand | Yes | — |
Branding for the hosted login page (docs/auth.md §4). If you need finer branding, build your own page and call the JSON endpoints in §5. When every value is an empty string, the project name and the default design tokens are used.
| Field | Type | Required | Description |
|---|
name | string | Yes | The name shown at the top of the page. Empty falls back to the project name |
logo | string | Yes | The logo image URL. Empty means no logo is drawn |
color | string | Yes | The accent color #rrggbb. Empty falls back to the @runlot/design default |
One registered login provider. The secret is not included. The client ID is a public value that is exposed to the browser anyway, while the client secret is encrypted and appears in no response (the rule for secret surfaces, docs/access.md §6).
| Field | Type | Required | Description |
|---|
provider | AuthProvider | Yes | — |
clientId | string | Yes | The public id issued by the provider |
updatedAt | string | Yes | The time it was last registered or replaced |
The end-user auth state of the project (docs/auth.md §9).
The user table is not part of this API. User data is stored in the runlot_auth schema of the project's Postgres (§6), and that table is handled together with project deletion, pg export, generation backups and restores. If the CP served this table, the data lifecycle would be split in two.
| Field | Type | Required | Description |
|---|
granted | boolean | Yes | Whether auth has been enabled. The existence of the enablement record is the only representation, so there is no "configured but off" state. When it is off, settings holds the defaults and providers is an empty array. |
settings | AuthSettings | Yes | — |
providers | AuthProviderMeta[] | Yes | The registered login providers. Sorted 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>. Every time you attach a host, add its callback URL to the login provider. That is why the dashboard and the CLI hand you this list verbatim. On a host you missed, login fails only on that host, and the reason is visible only in the provider's console. There are two built-in hostnames — <project>.<org>.<domain> and the joined legacy name <project>--<org>.<domain>. Deploy registers both, so links already handed out keep working, which is why both need a callback URL. |
A partial update of the settings. Only the fields you send change. Sending the whole settings object for every single switch would let a later save overwrite an earlier one when two users edit the same settings.
| Field | Type | Required | Description |
|---|
settings | object | Yes | A subset of AuthSettings. An empty object changes nothing |
The credentials of a login provider. Replacement goes through the same PUT, so both values are required. If you sent only the ID and omitted the secret, the response could not tell you whether the previous secret survived.
| Field | Type | Required | Description |
|---|
clientId | string | Yes | The public id issued by the provider |
clientSecret | string | Yes | Stored encrypted. It never appears in any response again. If you lose it, issue a new one in the login provider's console and PUT it again. |
One hostname connected to the project. state moves pending_dns (verifying ownership) → pending_tls (issuing the certificate) → active (connected), and the failure state is error. Only active records are in the routing table.
| Field | Type | Required | Description |
|---|
hostname | string | Yes | The name normalized to lowercase punycode |
kind | string | Yes | default is the built-in name — both <project>.<org>.<app-domain> and the joined legacy alias <project>--<org>.<app-domain> are this kind. custom is the customer's own domain |
state | string | Yes | — |
lastError | string | No | Why the last check failed. Users read it while fixing their own DNS |
createdAt | string | Yes | — |
verify | HostnameVerify | No | — |
The two records the customer has to add to their own DNS. Either one of them is enough to prove ownership — the CNAME also completes the connection, so that is the default guidance. The values are computed and returned by the CP (the fallback origin is a placement setting, so if clients wrote it down themselves they would diverge 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 | — |
One zone in our authoritative DNS (docs/domains.md §4.3). serial is the revision — it goes up on every change, and once publishedSerial catches up, the nameservers are serving 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. Lower than serial means it is still being applied (usually 2 seconds) |
dnssec | string | Yes | — |
ds | string[] | Yes | The DS record to publish at the parent. If we are the registrar, D3 publishes it; otherwise the UI shows it |
delegation | string | Yes | The result of the periodic check: whether the NS records at the registrar point at us |
delegationCheckedAt | string | No | — |
nameservers | string[] | Yes | The NS records to enter at the registrar. Not the zone's own values but a copy of the CP configuration |
createdAt | string | Yes | — |
One zone and all of its records.
Type: Zone & object
The set of values for one (name, type) pair. If managedBy is present the row was created by another layer and is locked in the editor — system (the zone's NS), hostname:<host> (app binding), 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 price for one name. There is no wholesale price — cost is a fact of our operations, not a fact for the customer (cp-core does return it for operators).
| Field | Type | Required | Description |
|---|
name | string | Yes | — |
available | boolean | Yes | — |
premium | boolean | Yes | A name the registry has priced separately. Registration and renewal prices differ widely |
priceCents | integer | Yes | First registration, 1 year (not KRW — cents) |
renewalCents | integer | Yes | Renewal, 1 year |
Registrant contact. It belongs to the customer — the ICANN verification mail goes to this address, and if it is not confirmed within 15 days the domain is suspended. The screen must say so 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 |
One domain. Expiry, lock and nameservers are a copy of the registrar's authoritative record, reconciled by domain-sync (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 means the domain sits in someone else's account — renewal and locking are not ours to do |
state | string | Yes | Derived from the expiry date: expiring from 30 days before expiry, expired for the 30-day grace period after it, redemption for the 30 days after that (redemption fee), then released. transferred_out is when the registrar answered "not in our account". |
expiresAt | string | No | — |
autoRenew | boolean | Yes | This is the customer's intent. Auto-renew on the registrar side is always on — we renew first and bill afterwards (§5.4). |
locked | boolean | Yes | Transfer lock |
nameservers | string[] | Yes | — |
years | integer | Yes | — |
priceCents | integer | No | The price the customer paid |
sagaStep | string | Yes | Anything other than done means the purchase is still in progress (docs/domains.md §5.3) |
sagaError | string | No | Why it stopped. It can be present even when the state is not failed |
createdAt | string | Yes | — |
One ledger line (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. Transfer 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 | — |