Runlog docs

Start building

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

Start

Retrieval

For every read an agent makes. Every read here is packed to a token budget you set and every row carries the ids it came from, so an answer built on it can cite its source rather than assert it.

POST/v1/api/actionsAPI key

Search the spine

Rank the project's derived cells, entities, and artifacts against a query, packed to a budget.

With a `query`, this ranks and packs. Without one it pages the raw collection — `{"input": {"kind": "cells"}}` returns one bounded page plus a `nextCursor`, and `kind: "entities"` selects the entity collection instead.

Every returned row carries its own ids, which is what lets a downstream answer cite the exact cell it rested on.

**Tether the read when you know what it should rest on.** `fileIds` and `entities` are the two ids ingestion hands back, and passing either narrows the walk to what those files and entity names back. This is how you ask a question of one uploaded drawing rather than of the whole project.

**Ask for a shape and you get one.** Pass a JSON Schema as `schema` and the response carries a `structured` object conforming to it, synthesized from the same ranked rows — which still come back beside it, so every field can be checked against what it was derived from. The natural-language `query` is still required: it decides what is retrieved, and the schema only decides how the answer is laid out over it.

Body

FieldTypeRequiredDescription
actionstringrequiredMust be `"memory_query"`.
input.kindstringoptionalList mode: what to read — 'cells' (default) or 'entities'.
input.querystringoptionalNatural-language question or task; when present, results come ranked by relevance to it instead of as a paged dump.
input.budgetnumberoptionalQuery mode: approximate token ceiling the ranked response packs to. Leave it unset unless your context is genuinely smaller than the model window — unset returns every ranked row retrieval found, bounded only by the model's own input window.
input.cursorstringoptionalList mode: the nextCursor from a prior dispatch, to continue the window.
input.fileIdsstring[]optionalQuery mode: tether the answer to these ingested files — only knowledge derived from them is returned. Use the fileIds ingestion handed back.
input.entitiesstring[]optionalQuery mode: tether the answer to these entity names — each one is seeded into the spine walk alongside the entities the query itself names. Use the entity names ingestion handed back.
input.schemaobjectoptionalQuery mode: a JSON Schema the answer must conform to. With it, the response carries a `structured` object matching the schema, grounded in the same retrieved rows; without it the response is the retrieved rows alone.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/api/actions" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "memory_query",
  "input": {
    "budget": 4096,
    "entities": [
      "North Elevation"
    ],
    "fileIds": [
      "4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN"
    ],
    "query": "what is the total glazed area and the floor count",
    "schema": {
      "properties": {
        "floorCount": {
          "type": "integer"
        },
        "glazedAreaSqFt": {
          "type": "number"
        }
      },
      "required": [
        "glazedAreaSqFt",
        "floorCount"
      ],
      "type": "object"
    }
  }
}'
Response · 200
{
  "action": "memory_query",
  "result": {
    "artifacts": [
      {
        "artifactId": "art_5Hn2Kp",
        "title": "Envelope takeoff"
      }
    ],
    "cells": [
      {
        "cellId": "c_7Hq2Rm",
        "text": "The north elevation carries 12,480 sq ft of glazing across six occupied floors."
      }
    ],
    "entities": [
      {
        "entityId": "e_2Kd9Lp",
        "kind": "component",
        "name": "North Elevation"
      }
    ],
    "query": "what is the total glazed area and the floor count",
    "structured": {
      "floorCount": 6,
      "glazedAreaSqFt": 12480
    }
  }
}

Errors

StatuserrorWhen
400action is requiredThe body named no action.
400query is required when a schema is suppliedA schema was sent without a query. A shape alone names nothing to retrieve.
403action not available to an API keyThe key's tier does not reach this action.
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.
POST/v1/api/actionsAPI key

Code context pack

Assemble the declarations, files, and relationships a stated task needs, packed to a token budget.

This is the endpoint an editor integration calls before a completion or a refactor. You describe the task in words and optionally seed it with the symbols and files the user is looking at; the response is a ranked, budget-bounded pack rather than a similarity dump.

`budget` is approximate tokens. Ask for what your model can actually spend — the pack fills the budget rather than truncating a fixed-size result. Omit it and the pack fills the model's own input window, so an unstated budget returns everything the ranking earned rather than a silent cut.

Dispatched through the action endpoint: `{"action": "code_context_pack", "input": {...}}`.

Body

FieldTypeRequiredDescription
actionstringrequiredMust be `"code_context_pack"`.
input.taskstringrequiredThe coding task in prose — what is being changed, in the words the codebase itself uses.
input.seedSymbolsstringoptionalDeclarations already in your context, comma- or newline-separated; they steer the ranking and are never returned back to you.
input.seedFilesstringoptionalFile paths already open in your context, comma- or newline-separated; same effect as seed symbols.
input.budgetnumberoptionalApproximate token ceiling the pack fills. Leave it unset unless your context is genuinely smaller than the model window — unset fills to the model's own input window.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/api/actions" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "code_context_pack",
  "input": {
    "budget": 8000,
    "seedFiles": [
      "Sources/Net/Router.swift"
    ],
    "seedSymbols": [
      "Router.send",
      "CheckoutClient"
    ],
    "task": "add retry with exponential backoff to the checkout network client"
  }
}'
Response · 200
{
  "action": "code_context_pack",
  "result": {
    "budget": 8000,
    "declarations": [
      {
        "body": "func send<T: Decodable>(_ req: URLRequest) async throws -> T { ... }",
        "fileId": "4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN",
        "filepath": "Sources/Net/Router.swift",
        "kind": "method",
        "lines": [
          88,
          131
        ],
        "symbol": "Router.send"
      }
    ],
    "files": [
      {
        "fileId": "4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN",
        "filepath": "Sources/Net/Router.swift",
        "reason": "seed"
      }
    ],
    "used": 7412
  }
}

Errors

StatuserrorWhen
400action is requiredThe body named no action.
403action not available to an API keyThe key's tier does not reach this action.
404unknown actionThe action id is not in the catalog.
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.
POST/v1/api/actionsAPI key

Find references

Resolve every declaration and usage site matching a symbol query.

Dispatched as `{"action": "code_references", "input": {"query": "..."}}`. Use it for jump-to-definition and find-usages against the derived graph rather than a text scan.

Body

FieldTypeRequiredDescription
actionstringrequiredMust be `"code_references"`.
input.querystringoptionalThe prefix typed so far; empty lists the first page alphabetically.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/api/actions" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "code_references",
  "input": {
    "query": "CheckoutClient"
  }
}'
Response · 200
{
  "action": "code_references",
  "result": {
    "references": [
      {
        "filepath": "Sources/Net/CheckoutClient.swift",
        "kind": "declaration",
        "lines": [
          12,
          96
        ],
        "symbol": "CheckoutClient"
      },
      {
        "filepath": "Sources/App/AppDelegate.swift",
        "kind": "usage",
        "lines": [
          41,
          41
        ],
        "symbol": "CheckoutClient.init"
      }
    ]
  }
}

Errors

StatuserrorWhen
400action is requiredThe body named no action.
403action not available to an API keyThe key's tier does not reach this action.
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.

The spine, directly

The three node kinds under the ranked reads above, each addressable by the ids any answer cites. This is how a citation stops being an id and becomes a place: a cell says which files back it, a faction says which lines of them it was cut from.

GET/v1/projects/{projectId}/cellsAPI key

Read the spine directly

Page the project's derived cells, optionally scoped to one file, entity, or parent cell.

A cell is one derived claim with its provenance attached. This is the raw graph read under the ranked retrieval above — reach for it when you want a selection's cells rather than a ranked answer.

Every read pages on `X-Next-Cursor`, the scoped ones (`fileId`, `entityId`, `factionId`, `parentCellId`) included; follow the cursor to read a selection whole. A scoped read's first page carries the selection's count on `X-Total-Count`, left off where it cannot be exact: a `factionId` or `fileId` read narrowed by `entityId`, and a `fileId` read whose files hold more than 30 sections.

`schemaId` pages the cells a type's fields were filled into; with `entityId` beside it the page narrows to that one entity's filled cell, which carries the type's field names as keys plus `schemaVersion`.

The first page carries the ancestor-visibility union, so a child project sees its parents' cells inline, each row keeping its own `projectId`.

Path parameters

FieldTypeRequiredDescription
projectIdstringrequiredThe project to read.

Query parameters

FieldTypeRequiredDescription
fileIdstringoptionalPage the cells derived from these files, comma-separated.
entityIdstringoptionalPage the cells backing this entity; beside `factionId` or `fileId` it narrows them to the cells that also back it.
schemaIdstringoptionalPage the cells filled against this type entity's fields; add `entityId` to read one entity's.
parentCellIdstringoptionalPage this cell's children.
cursorstringoptionalCursor from a previous page.
limitintegeroptionalPage size, capped at 200.
Request · curl
curl -X GET "https://runlog-7613480744.us-central1.run.app/v1/projects/p7Kd2mQx/cells?fileId=4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
[
  {
    "cellId": "c_7Hq2Rm",
    "entityIds": [
      "e_2Kd9Lp"
    ],
    "fileIds": [
      "4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN"
    ],
    "projectId": "p7Kd2mQx",
    "text": "Router.send retries once on a 5xx before surfacing the transport error.",
    "updatedAt": "2026-08-07T18:24:31Z"
  }
]

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.
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}/entitiesAPI key

Read the entity graph

Page the entities the project's cells resolve to — the nodes a code graph hangs off.

Only entities backing at least one cell are returned, so the graph never carries nodes nothing rests on.

Path parameters

FieldTypeRequiredDescription
projectIdstringrequiredThe project to read.

Query parameters

FieldTypeRequiredDescription
entityTypestringoptionalPage only the entities of this type, e.g. `Person`; `Category` lists the types themselves.
rolestringoptionalPage only the entities carrying this role, e.g. `Self`, `Competitor`, `Lead`, `AE`. Combines with `entityType`, and stands alone for every entity carrying the role whatever its type.
qstringoptionalReturn the entities whose names best match these words, best first, up to `limit`; unpaginated.
cursorstringoptionalCursor from a previous page.
limitintegeroptionalPage size, capped at 200.
Request · curl
curl -X GET "https://runlog-7613480744.us-central1.run.app/v1/projects/p7Kd2mQx/entities" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
[
  {
    "cellCount": 9,
    "entityId": "e_2Kd9Lp",
    "kind": "component",
    "name": "CheckoutClient",
    "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.
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}/factionsAPI key

Resolve cited spans

Resolve faction ids to the file and the line span each was cut from — the place behind a citation.

`factionIds` is the form a cited answer uses: pass the ids a turn cited and the whole set comes back in one batched read, unpaginated and in no guaranteed order — match the rows back by `factionId`.

`sourceLocation` is the anchor. A code or text faction carries `lineStart`/`lineEnd`, a PDF one `pdfPageStart`/`pdfPageEnd`, a media one `audioStartMs`/`videoStartMs`. A dimension that does not apply to the asset is absent rather than zero, so a missing span is readable as "not anchored" rather than as line 0.

`fileIds` is derived reach, not a stored field: it is the files whose cells reference this faction. A faction that no cell has reached yet comes back with none.

A file-scoped read (`fileId`) is one-shot and complete, bounded by the file; the unscoped read and the `categoryEntityId` and `entityId` reads page on `X-Next-Cursor`, an `entityId` read's first page counting the entity's sections on `X-Total-Count`.

Path parameters

FieldTypeRequiredDescription
projectIdstringrequiredThe project to read.

Query parameters

FieldTypeRequiredDescription
factionIdsstringoptionalComma-separated faction ids — the cited set, resolved complete and unpaginated in one read.
fileIdstringoptionalReturn every faction this file's cells reach, complete and unpaginated.
categoryEntityIdstringoptionalPage the factions read as this type entity, on `X-Next-Cursor`.
entityIdstringoptionalPage the factions this entity's reading named.
factionTypestringoptionalKeep only this kind, e.g. `code_unit`, `text`, `table`.
cursorstringoptionalCursor from a previous page.
limitintegeroptionalPage size, capped at 200.
Request · curl
curl -X GET "https://runlog-7613480744.us-central1.run.app/v1/projects/p7Kd2mQx/factions?factionIds=fa_3Qm8Xz,fa_9Rt2Kd" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
[
  {
    "factionId": "fa_3Qm8Xz",
    "factionType": "code_unit",
    "fileIds": [
      "4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN"
    ],
    "inferredType": "text/code/python",
    "sourceLocation": {
      "lineEnd": 131,
      "lineStart": 88
    }
  }
]

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.
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.