Runlog docs

Start building

A plan buys a weekly allowance, sized for steady work.

Start

Projects

A project is the memory boundary. Every file you ingest, every cell the pipeline derives, and every retrieval you run is scoped to one project id, which rides on the `X-Project-Id` header of every request. Projects are non-enumerable: a project you cannot reach answers 404, never 403.

Create and read

Provisioning: mint a project per customer or per repository, list what the key's owner reaches, read one back. These run on an account key, so an integrator provisions without a browser.

POST/v1/projectsAPI key

Create or update a project

Upsert a project — a body without `projectId` mints a new 8-character id.

The same route creates and updates. Send a `projectId` you already own to rename it or re-parent it; omit it to create.

On an account key this is the provisioning call: creating needs no `X-Project-Id` (there is no project yet) and requires an Editor-level key. The signed-in session calls the same route.

`parentProjectIds` establishes inheritance: a child project reads its ancestors' cells and entities through the same retrieval calls, so a per-repository child under one organization parent gives every child the shared context without copying it.

Body

FieldTypeRequiredDescription
namestringrequiredHuman-readable project name.
projectIdstringoptionalExisting project to update. Omit to mint a new id.
parentProjectIdsstring[]optionalProjects this one inherits readable context from. Each must be a project you can access.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/projects" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "LingCode — acme/checkout",
  "parentProjectIds": [
    "orgRootA1"
  ]
}'
Response · 200
{
  "ancestorProjectIds": [
    "orgRootA1"
  ],
  "createdAt": "2026-08-07T18:20:04Z",
  "name": "LingCode — acme/checkout",
  "ownerUid": "u_8Kd0Zq",
  "parentProjectIds": [
    "orgRootA1"
  ],
  "projectId": "p7Kd2mQx",
  "updatedAt": "2026-08-07T18:20:04Z"
}

Errors

StatuserrorWhen
400name is requiredThe body carried no `name`.
404project not foundA named parent is not reachable by you. Answering 404 rather than 403 keeps projects non-enumerable — you cannot probe a parent's existence.
400invalid request bodyThe body is not JSON, or exceeds the 1 MB request-body cap.
401invalid API keyThe `Authorization` header is missing, malformed, or names a revoked key.
400name the target project via the X-Project-Id header or projectId queryNo project was named and the key is not bound to one.
429rate limit exceededYou passed 60 requests/minute on this key. `Retry-After` carries the seconds to wait.
500internal errorThe relay failed to serve the request. Retry with backoff; the cause is logged server-side against your request.
GET/v1/projectsAPI key

List projects

One cursor page of the projects you own, plus public and shared ones.

The body is a bare array. The next page's cursor rides back on the `X-Next-Cursor` response header — when the header is absent you have reached the end.

Pages order by document id, which is stable across concurrent writes, so paging to exhaustion never skips or repeats a project.

Query parameters

FieldTypeRequiredDescription
cursorstringoptionalOpaque cursor from a previous page's `X-Next-Cursor` header.
limitintegeroptionalPage size. Capped at 200.
Request · curl
curl -X GET "https://runlog-7613480744.us-central1.run.app/v1/projects?limit=50" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
[
  {
    "name": "LingCode — acme/checkout",
    "projectId": "p7Kd2mQx",
    "updatedAt": "2026-08-07T18:20:04Z"
  },
  {
    "name": "Acme — org root",
    "projectId": "orgRootA1",
    "updatedAt": "2026-08-06T09:11:52Z"
  }
]

Errors

StatuserrorWhen
400invalid cursorThe cursor was tampered with or came from a different account.
401invalid API keyThe `Authorization` header is missing, malformed, or names a revoked key.
400name the target project via the X-Project-Id header or projectId queryNo project was named and the key is not bound to one.
429rate limit exceededYou passed 60 requests/minute on this key. `Retry-After` carries the seconds to wait.
500internal errorThe relay failed to serve the request. Retry with backoff; the cause is logged server-side against your request.
GET/v1/projects/{projectId}API key

Get a project

Read one project record by id.

Path parameters

FieldTypeRequiredDescription
projectIdstringrequiredThe project to read.
Request · curl
curl -X GET "https://runlog-7613480744.us-central1.run.app/v1/projects/p7Kd2mQx" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
{
  "ancestorProjectIds": [
    "orgRootA1"
  ],
  "createdAt": "2026-08-07T18:20:04Z",
  "name": "LingCode — acme/checkout",
  "ownerUid": "u_8Kd0Zq",
  "projectId": "p7Kd2mQx",
  "updatedAt": "2026-08-07T18:22:10Z"
}

Errors

StatuserrorWhen
404project not foundThe project does not exist, or you have no access to it. Projects are non-enumerable, so a denied read is indistinguishable from a missing one.
405method not allowedThe path exists but does not serve this verb.
401invalid API keyThe `Authorization` header is missing, malformed, or names a revoked key.
400name the target project via the X-Project-Id header or projectId queryNo project was named and the key is not bound to one.
429rate limit exceededYou passed 60 requests/minute on this key. `Retry-After` carries the seconds to wait.
500internal errorThe relay failed to serve the request. Retry with backoff; the cause is logged server-side against your request.
POST/v1/projects/ensureAPI key

Ensure a first project

Return the caller's project, creating one if they have none — the idempotent bootstrap call.

Call this once at the start of an integration rather than branching on an empty list. It is idempotent: a caller who already has a project gets it back unchanged.

Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/projects/ensure" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{}'
Response · 200
{
  "created": false,
  "name": "My workspace",
  "projectId": "p7Kd2mQx"
}

Errors

StatuserrorWhen
401invalid API keyThe `Authorization` header is missing, malformed, or names a revoked key.
400name the target project via the X-Project-Id header or projectId queryNo project was named and the key is not bound to one.
429rate limit exceededYou passed 60 requests/minute on this key. `Retry-After` carries the seconds to wait.
500internal errorThe relay failed to serve the request. Retry with backoff; the cause is logged server-side against your request.

Publish and delete

The two owner acts: exposing a project to every signed-in reader, and retiring it. Both stay on the signed-in session — a leaked key must not be able to publish or destroy a corpus.

POST/v1/projects/{projectId}/visibilityAPI key

Publish a project, or take it back

Let anyone signed in read this project, or return it to the people named on it.

Public grants VIEWER and nothing else: a reader can retrieve and cite, and every write still requires a principal that names them. Sharing a link never hands out an editor.

Anonymous readers are never admitted. "Public" means anyone who signs in, so a shared project asks a stranger for an account and no more than that.

Owner only. Every other tier was granted by someone, and exposing a corpus is not a power a collaborator inherits along with editing.

Body

FieldTypeRequiredDescription
publicbooleanrequiredTrue publishes to every signed-in reader; false returns the project to its named principals.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/projects/p7Kd2mQx/visibility" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "public": true
}'
Response · 200
{
  "public": true,
  "status": "ok"
}

Errors

StatuserrorWhen
404project not foundThe project does not exist, or you are not its owner. Answering 404 rather than 403 keeps projects non-enumerable.
400invalid request bodyThe body is not JSON, or exceeds the 1 MB request-body cap.
401invalid API keyThe `Authorization` header is missing, malformed, or names a revoked key.
400name the target project via the X-Project-Id header or projectId queryNo project was named and the key is not bound to one.
429rate limit exceededYou passed 60 requests/minute on this key. `Retry-After` carries the seconds to wait.
500internal errorThe relay failed to serve the request. Retry with backoff; the cause is logged server-side against your request.
DELETE/v1/projects/{projectId}API key

Delete a project

Soft-delete a project and drop it from every list and stream.

The delete is soft: the record is retired rather than erased, so an in-flight ingestion cannot resurrect a half-deleted project.

Content-addressed chunk blobs are global and shared across projects, so deleting a project never deletes a chunk another project still references.

Path parameters

FieldTypeRequiredDescription
projectIdstringrequiredThe project to delete.
Request · curl
curl -X DELETE "https://runlog-7613480744.us-central1.run.app/v1/projects/p7Kd2mQx" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
{
  "deleted": true,
  "projectId": "p7Kd2mQx"
}

Errors

StatuserrorWhen
404project not foundThe project does not exist, or you have no access to it. Projects are non-enumerable, so a denied read is indistinguishable from a missing one.
403editor access requiredYou can read the project but not write to it.
401invalid API keyThe `Authorization` header is missing, malformed, or names a revoked key.
400name the target project via the X-Project-Id header or projectId queryNo project was named and the key is not bound to one.
429rate limit exceededYou passed 60 requests/minute on this key. `Retry-After` carries the seconds to wait.
500internal errorThe relay failed to serve the request. Retry with backoff; the cause is logged server-side against your request.