Runlog docs

Start building

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

Start

Agent sessions

For an open-ended goal. A run is a durable session bound to one project: you open it once, append messages, and read the answer back as a single JSON read or as a live stream — the agent decomposes the goal, retrieves repeatedly, and names the declarations each claim rested on. Nothing on this path mutates a codebase: when a turn concludes a change is needed it writes a plan — the reasoning, the proposed changes, the terms it read without changing — and stops. Applying it is a separate, explicit approval on the same run, which is what makes the surface safe to put behind an editor's "apply" button.

Ask

The question path: open a run, append the question, read or stream the answer with its citations.

POST/v1/api/agent-runsAPI key

Open a query run

Start a conversation bound to one project.

A run is cheap and durable. Open one per user conversation and keep appending; do not open one per question.

Body

FieldTypeRequiredDescription
projectIdstringoptionalThe codebase the run asks about. A run without one has no project memory to read.
titlestringoptionalDisplay title.
toolstringoptionalWhich capability set the run may reach. Defaults to the general set.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/api/agent-runs" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "projectId": "p7Kd2mQx",
  "title": "Why does checkout retry?",
  "tool": "atlas"
}'
Response · 200
{
  "createdAt": "2026-08-07T18:30:00Z",
  "projectId": "p7Kd2mQx",
  "runId": "run_9Fb3TnQ2",
  "status": "open",
  "title": "Why does checkout retry?"
}

Errors

StatuserrorWhen
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/agent-runs/{runId}/messagesAPI key

Ask a question

Append a user message to a run and get the answer, with the cells and declarations it cited.

The answer is grounded: `source` parts carry the cell ids the claim rested on, so you can render a citation the user can click through to a file and line rather than a confidence score.

For long answers, open the stream instead — this route returns once the whole turn has settled.

Every message carries `metadata.createdAt` — when it was written, as an ISO-8601 UTC instant with milliseconds. It is what orders a thread and what measures how long a turn took.

Path parameters

FieldTypeRequiredDescription
runIdstringrequiredThe run to append to.

Body

FieldTypeRequiredDescription
textstringrequiredThe question.
fileIdsstring[]optionalFiles to put in front of the model as explicit context.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/api/agent-runs/run_9Fb3TnQ2/messages" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "fileIds": [
    "4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN"
  ],
  "text": "Why does the checkout client retry on a declined card?"
}'
Response · 200
{
  "assistantMessage": {
    "id": "m_2",
    "metadata": {
      "createdAt": "2026-02-11T09:14:22.418Z"
    },
    "parts": [
      {
        "text": "It retries once against the fallback processor, because a soft decline from the primary is recoverable while a hard decline is not.",
        "type": "text"
      },
      {
        "sourceId": "c_7Hq2Rm",
        "title": "Sources/Net/CheckoutClient.swift:88-131",
        "type": "source-document"
      }
    ],
    "role": "assistant"
  },
  "userMessage": {
    "id": "m_1",
    "metadata": {
      "createdAt": "2026-02-11T09:14:22.418Z"
    },
    "parts": [
      {
        "text": "Why does the checkout client retry on a declined card?",
        "type": "text"
      }
    ],
    "role": "user"
  }
}

Errors

StatuserrorWhen
400text is requiredThe message carried no content.
404not foundNo such run, or it belongs to another account.
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/agent-runs/{runId}Session

Read a run

Read the whole conversation — every message with its parts and citations.

Path parameters

FieldTypeRequiredDescription
runIdstringrequiredThe run to read.
Request · curl
curl -X GET "https://runlog-7613480744.us-central1.run.app/v1/agent-runs/run_9Fb3TnQ2" \
  -H "Authorization: Bearer $RUNLOG_SESSION_TOKEN" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
{
  "messages": [
    {
      "id": "m_2",
      "metadata": {
        "createdAt": "2026-02-11T09:14:22.418Z"
      },
      "parts": [
        {
          "text": "It retries once against the fallback processor…",
          "type": "text"
        }
      ],
      "role": "assistant"
    }
  ],
  "pendingWrites": [],
  "run": {
    "projectId": "p7Kd2mQx",
    "runId": "run_9Fb3TnQ2",
    "status": "open"
  }
}

Errors

StatuserrorWhen
404not foundNo such run, or it belongs to another 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.
POST/v1/api/agent-runs/{runId}/stream-ticketAPI key

Mint a stream ticket

Receive the short-lived ticket that opens the run's stream without a header.

A normal keyed call. The ticket opens the stream once, within 120 seconds, so the key itself never travels in a URL. Mint one per connection.

Path parameters

FieldTypeRequiredDescription
runIdstringrequiredThe run to stream.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/api/agent-runs/run_9Fb3TnQ2/stream-ticket" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
{
  "expiresInSeconds": 120,
  "ticket": "st_7Qk2Mv…"
}

Errors

StatuserrorWhen
404not foundNo such run, or it belongs to another 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/api/agent-runs/{runId}/streamAPI key

Stream an answer

Receive the answer as it forms, over server-sent events.

Two credentials open it: the API key as a bearer header, or a stream ticket on the query string for a client that cannot set a header (EventSource). The ticket comes from POST /v1/api/agent-runs/{runId}/stream-ticket, opens the stream once within 120 seconds, and the stream then lives as long as the connection.

The stream speaks the AI SDK UI message-stream protocol — `data:`-only SSE frames with a `type` discriminator — announced by the `x-vercel-ai-ui-message-stream: v1` response header. Any client built against that protocol consumes it unchanged.

Every part event carries the stable id `messageId:partIdx`, so a late-arriving part places itself correctly regardless of arrival order.

Reconnecting replays every finished turn from persistence before any new delta, so a dropped connection never loses an answer.

Path parameters

FieldTypeRequiredDescription
runIdstringrequiredThe run to watch.

Query parameters

FieldTypeRequiredDescription
ticketstringoptionalA stream ticket from the stream-ticket route, in place of the bearer header. Opens once, within 120 seconds.
The flow
# 1. Mint a ticket: a normal keyed call. It opens the stream once, within two minutes.
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/api/agent-runs/run_9Fb3TnQ2/stream-ticket" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
# → {"ticket": "st_7Qk2Mv…", "expiresInSeconds": 120}

# 2. Open the stream with it. No header, so EventSource can; it lives as long as the connection.
curl -N "https://runlog-7613480744.us-central1.run.app/v1/api/agent-runs/run_9Fb3TnQ2/stream?ticket=$TICKET"
Request · curl
curl -X GET "https://runlog-7613480744.us-central1.run.app/v1/api/agent-runs/run_9Fb3TnQ2/stream" \
  -H "Authorization: Bearer $RUNLOG_API_KEY" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
data: {"type":"text-delta","id":"m_2:0","delta":"It retries once against"}

Errors

StatuserrorWhen
404not foundNo such run, or it belongs to another 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.

Steer

The control path: stop a turn in flight, ask for proposed changes, read the plan of record, and approve or reject each staged write by id.

POST/v1/agent-runs/{runId}/interruptSession

Interrupt a run

Stop the turn in flight — the cancel behind a user hitting stop.

Path parameters

FieldTypeRequiredDescription
runIdstringrequiredThe run to interrupt.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/agent-runs/run_9Fb3TnQ2/interrupt" \
  -H "Authorization: Bearer $RUNLOG_SESSION_TOKEN" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{}'
Response · 200
{
  "interrupted": true,
  "runId": "run_9Fb3TnQ2"
}

Errors

StatuserrorWhen
404not foundNo such run.
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/agent-runs/{runId}/messagesSession

Request proposed changes

Ask for a change; the run answers with a plan and staged pending writes rather than applying anything.

This is the same message route as a question. The difference is the ask: a request to change something produces a plan of record persisted beside the turn, and the proposed writes are staged onto the run rather than executed.

Read the staged proposals off the run's `pendingWrites`, show them to the user, then approve or reject each one by id.

Path parameters

FieldTypeRequiredDescription
runIdstringrequiredThe run to append to.

Body

FieldTypeRequiredDescription
textstringrequiredThe change you want proposed.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/agent-runs/run_9Fb3TnQ2/messages" \
  -H "Authorization: Bearer $RUNLOG_SESSION_TOKEN" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "Add exponential backoff to Router.send and update its callers."
}'
Response · 200
{
  "assistantMessage": {
    "id": "m_4",
    "parts": [
      {
        "text": "Proposed 2 changes across 2 files. Nothing applied.",
        "type": "text"
      }
    ],
    "role": "assistant"
  },
  "pendingWrites": [
    {
      "fileId": "4mHqZ1nR8vTbC0sWyLpAeGkJ3dXfU6iO2rN",
      "id": "pw_3Rt7Yu",
      "kind": "cell_edit",
      "summary": "Router.send — wrap the request in a backoff loop capped at 3 attempts"
    }
  ]
}

Errors

StatuserrorWhen
404not foundNo such run.
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/agent-runs/{runId}Session

Read a plan artifact

Read the plan of record and the writes still staged for approval.

The plan is persisted, not discarded — it names the changes it would make and the terms it read without changing, each citing what it rests on. A plan you did not approve is still readable later.

Path parameters

FieldTypeRequiredDescription
runIdstringrequiredThe run holding the plan.
Request · curl
curl -X GET "https://runlog-7613480744.us-central1.run.app/v1/agent-runs/run_9Fb3TnQ2" \
  -H "Authorization: Bearer $RUNLOG_SESSION_TOKEN" \
  -H "X-Project-Id: p7Kd2mQx"
Response · 200
{
  "pendingWrites": [
    {
      "id": "pw_3Rt7Yu",
      "kind": "cell_edit",
      "summary": "Router.send — wrap the request in a backoff loop capped at 3 attempts"
    },
    {
      "id": "pw_8Kq1Za",
      "kind": "source_transition",
      "summary": "Promote Artifacts/design/retry-note.md to source"
    }
  ],
  "run": {
    "projectId": "p7Kd2mQx",
    "runId": "run_9Fb3TnQ2"
  }
}

Errors

StatuserrorWhen
404not foundNo such run.
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/agent-runs/{runId}/pending-writesSession

Approve or reject proposals

Decide each staged write by id; approved writes replay through the exact call the turn was refused.

An approval is a replay, not a re-derivation: the confirmed write runs the same service call the turn originally attempted, so what you approved is exactly what happens.

Decisions are per id. Approving a subset leaves the rest staged and readable — the response returns what remains.

Path parameters

FieldTypeRequiredDescription
runIdstringrequiredThe run holding the proposals.

Body

FieldTypeRequiredDescription
decisionstringrequired`"approve"` or `"reject"`.
writeIdsstring[]requiredThe staged write ids this decision covers.
Request · curl
curl -X POST "https://runlog-7613480744.us-central1.run.app/v1/agent-runs/run_9Fb3TnQ2/pending-writes" \
  -H "Authorization: Bearer $RUNLOG_SESSION_TOKEN" \
  -H "X-Project-Id: p7Kd2mQx" \
  -H "Content-Type: application/json" \
  -d '{
  "decision": "approve",
  "writeIds": [
    "pw_3Rt7Yu"
  ]
}'
Response · 200
{
  "applied": [
    "pw_3Rt7Yu"
  ],
  "remaining": [
    {
      "id": "pw_8Kq1Za",
      "kind": "source_transition"
    }
  ]
}

Errors

StatuserrorWhen
400decision must be approve or rejectAn unrecognised decision was sent.
404not foundNo such run, or none of the named write ids are staged on it.
403editor access requiredYou can read the project but not write to it.
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.