# Runlog API > The Runlog public API, indexed for agents: every route that serves today with the credential it takes, > the actions one dispatch route carries, and the workflows those routes compose into. It is a projection > of the same declarations that generate the app, the agent abilities, the MCP tools and the OpenAPI spec, > so it cannot describe a surface the API does not serve. Base URL: https://runlog-7613480744.us-central1.run.app OpenAPI 3.0 spec: https://www.runlogai.com/openapi.json Human-readable docs: https://www.runlogai.com/docs/api Generated by: go run ./local/go/cmd/api-docs-gen Only routes that serve today are listed. A route that is specified and not serving carries x-status: planned in the OpenAPI document and appears in the Coming tables of the human docs. A route marked ENTERPRISE answers 403 on a key without a signed agreement; the in-app session is unaffected. ## Credentials - API key: an account key (rlka_) or a project-bound key (rlk_) minted in the Runlog app, sent as Authorization: Bearer, with the target project on X-Project-Id. - Session: the signed-in Runlog session's Firebase ID token, which a key cannot mint. An API key at one of these routes answers 401. - Site key: a published site's own key, carried in the request body by page code running in a visitor's browser. ## Routes Paths are relative to the base URL. Format: METHOD path | credential | purpose A path carrying action= is that catalog action dispatched through the shared route; see Actions. ### Start keys and projects: everything here runs once, before any data moves. #### Keys docs: https://www.runlogai.com/docs/api/keys Three credentials reach this API. An account key (`rlka_`) authenticates you and names its target project per request, so it can never reach past what you can reach and loses a project the moment your access does. A project key (`rlk_`) is bound to one project at a fixed tier - what you hand a service that should reach that project and nothing else. A site key belongs to a published site's page code and authenticates the site, never a person. The first two are bearer tokens on any client, from any origin - a key does not care whether the caller was built with Runlog. ##### Account keys The `rlka_` key: your reach, per request. Mint one when the integration acts as you across projects. - POST /v1/api-keys | Session | Create an account API key. The plaintext is returned exactly once. - GET /v1/api-keys | Session | List your keys - metadata only, never plaintext. - DELETE /v1/api-keys/{keyId} | Session | Revoke a key immediately. ##### Project keys The `rlk_` key: one project at a fixed tier. Mint one when a service should reach exactly one project and nothing else. - POST /v1/mcp-keys | API key | Create a key bound to one project at a fixed access tier. - GET /v1/mcp-keys | Session | List your keys in one project - metadata only, never plaintext. - DELETE /v1/mcp-keys/{keyId} | Session | Revoke a project key and withdraw the project access it held. ##### Site keys The third credential lives beside the other two. A PFW is a website an owner builds in the Design Lens and publishes at a subdomain; its page code carries a site key, which is public by construction - anyone who views source has it - so it authenticates the site, never a person. The key's whole authority is the action list the owner published on it, floored at reads, and it reaches exactly one project. - POST /v1/public/sites/actions | Site key | Run one action the PFW's owner published, against the PFW's own project. #### Projects docs: https://www.runlogai.com/docs/api/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/projects | API key | Upsert a project - a body without `projectId` mints a new 8-character id. - GET /v1/projects | API key | One cursor page of the projects you own, plus public and shared ones. - GET /v1/projects/{projectId} | API key | Read one project record by id. - POST /v1/projects/ensure | API key | Return the caller's project, creating one if they have none - the idempotent bootstrap call. ##### 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}/visibility | API key | Let anyone signed in read this project, or return it to the people named on it. - DELETE /v1/projects/{projectId} | API key | Soft-delete a project and drop it from every list and stream. ### Ingestion what your data becomes: files, working directories, links, schemas, channels. #### Files docs: https://www.runlogai.com/docs/api/files For bytes you hold - on disk, in your own bucket, or as a whole codebase. Two ways in: hand over signed URLs and the relay fetches them, or content-address locally and upload only the chunks the server is missing. Either way bytes never pass through the relay as request bodies, which is what makes a 2 GB file and a one-line edit to it cost about the same on the wire, and the file id stays stable across every later edit. ##### Bring Getting bytes in, and watching them land. One call takes any number of files and answers the project's ingestion lease; a second call says where that lease stands. Both are keyed on the lease rather than on a file, so a corpus of ten thousand files is still one thing to send and one thing to watch. - POST /v1/projects/{projectId}/ingest | API key | Read one file or a whole corpus into a project, in a single call, and get back the lease reading it. - GET /v1/projects/{projectId}/ingest | API key | Ask where the project's ingestion lease stands: what it took, what is left, and what failed. - POST /v1/api/actions action=file_upload | API key | Register a single content-addressed file you already pushed to storage, in one dispatch. - POST /v1/projects/{projectId}/files/import | API key | Hand the relay a list of signed URLs; it fetches each one and ingests it into the named project. - POST /v1/projects/{projectId}/files | API key | Record the file against its ordered chunk ids and queue its first ingestion pass. - POST /v1/projects/{projectId}/files/chunks/missing | API key | Send the chunk manifest; get back only the chunk ids the server does not already hold, each with a signed upload URL. - POST /v1/projects/{projectId}/files/zip | API key | Register an uploaded archive; the server expands it, preserving each entry's relative path. ##### Browse Reading what the project holds: the file records, the folder tree derived from them, and the entity names an ingestion resolved - the ids every later retrieval tethers to. - GET /v1/projects/{projectId}/files | API key | One cursor page of the project's visible files, newest cursor on `X-Next-Cursor`. - GET /v1/projects/{projectId}/files/{fileId} | API key | Read one file record: where its reading got to, and everything the project knows about it. - GET /v1/projects/{projectId}/folders | API key | Read the project's folder tree, derived from the `filepath` every file carries. - GET /v1/projects/{projectId}/files/{fileId}/entities | API key | The names of every entity the file's ingestion derived - the other half of what an ingestion gives you back. ##### Edit Placement changes. A move rewrites the path and nothing else; content changes live in the Migration leg, because a new version of a file is a migration. - PATCH /v1/projects/{projectId}/files/{fileId} | API key | Change a file's name, title, or folder. A `filepath` change is a move. - PATCH /v1/projects/{projectId}/folders/{folderPath} | API key | Relocate a whole subtree in one batched prefix rewrite. - DELETE /v1/projects/{projectId}/files/{fileId} | API key | Soft-delete a file and retire the derivations that rested only on it. #### Working directory docs: https://www.runlogai.com/docs/api/workspace A folder on your machine becomes a project that stays current. The loop is four calls: ask what the key opens and what is never read, price the reading before it happens, compare against what the project already holds, then push only the files whose content actually moved. Content addressing is what makes the second run cost nothing - an unchanged file is never re-read and never re-sent. Credentials are refused by shape rather than by folder, so a key that lands in a synced directory is left on disk instead of being indexed. - GET /v1/api/workspace/describe | API key | Ask which project this key opens, what reads as code, and what is never read. - GET /v1/api/workspace/manifest | API key | List every file this working directory already put in the project, under its content address. - POST /v1/api/workspace/quote | API key | Ask what reading a given amount of new content will cost before any of it is sent. - POST /v1/api/workspace/sync | API key | Send one batch of changed files and learn which of them landed. - POST /v1/api/workspace/reconcile | API key | Tell the project which paths no longer exist on disk, so what it holds matches the folder. #### Links and conversations docs: https://www.runlogai.com/docs/api/links For content you can point at rather than upload: a URL the relay pulls and ingests, and - specified - a conversation pushed live from inside ChatGPT or Claude, so the assistant a buyer already uses becomes a feeder of the memory rather than a rival to it. - POST /v1/api/actions action=document_ingest | API key | Point the relay at a URL or file reference; it pulls the document into the project's corpus. #### Schemas docs: https://www.runlogai.com/docs/api/schemas For data that has a shape before it arrives. A record schema declares the typed records a project holds beside its files; a type's schema declares the fields every entity of that type is read into. ##### Records Not everything in a project is a file. A named object is a typed record - a form submission, a contact, a product, an owner-curated list - declared by a schema and held against it. Schemas and the submit action are reachable today; record CRUD over the API is specified and lands with the rest of the surface. - GET /v1/artifact-schemas | API key | Read the typed schemas the project's artifacts and records are held against. - POST /v1/artifact-schemas | API key | Create or update a typed schema the project's records are held against. - POST /v1/api/actions action=object_submit | API key | Write one typed record against the bound named-object type, validated on write. ##### Types Every entity type in a project is itself an entity carrying the type's schema: the typed fields each entity of that type is read into. An entity's `typeEntityId` names its type; editing the type's schema bumps its version, and every entity of the type is re-read against it the next time new material mentions it. - PUT /v1/projects/{projectId}/entities/{entityId}/schema | Session | Replace a type's fields, against the version the edit started from; the schema moves one version up. - POST /v1/projects/{projectId}/entities/{entityId}/schema/approve | Session | Make the change ingestion proposed for a type its live schema, one version up. - POST /v1/projects/{projectId}/entities/{entityId}/schema/reject | Session | Drop the change ingestion proposed for a type, keeping its schema. ##### Proposed types When the same record shape recurs across your files - a form, a table family, a message kind - ingestion proposes it as a type, citing where it was read. Nothing is bound to a proposal until you confirm it; a rejected shape is never proposed again. - GET /v1/projects/{projectId}/category-proposals | Session | Page the shapes awaiting your decision, newest first. - POST /v1/projects/{projectId}/category-proposals/{proposalId}/confirm | Session | Make a shape a type: its fields become the type's schema, and the sections it was read from are filed under it. - POST /v1/projects/{projectId}/category-proposals/{proposalId}/reject | Session | Refuse a shape; it is never proposed again. #### Channels docs: https://www.runlogai.com/docs/api/channels For data that lives in an outside account: Gmail and Microsoft 365 mailboxes and calendars, LinkedIn, Google Drive and OneDrive / SharePoint, Microsoft Copilot, and the social surfaces. Connecting one is a consent step that happens in the provider's own browser flow; everything after it is API-reachable, and synced content becomes ordinary corpus. The routes at the top are the same for every provider; the sections below them are what only a file store or Copilot can do here. What a channel SENDS lives in the Execution leg's Outbound lane, where the approval gate is the story. - GET /v1/user/channels | Session | Read which channels are connected, their health, and when each last synced. - POST /v1/channels | Session | Start a channel connection and receive the consent link to send the account holder to. - POST /v1/channel-sync/{channelId} | API key | Pull the channel's history into the project's corpus, where retrieval reaches it. ### Migration change without re-ingestion: source changes, schema versions, watching, API versions. #### Source changes docs: https://www.runlogai.com/docs/api/source-changes For a file whose content moved. The single-file surgical update is deliberately open on every plan - a new version re-ingests only its delta, unchanged regions cost nothing, and every reference to the file survives because the file id never changes. Reingest re-runs derivation without re-uploading a byte. - PUT /v1/projects/{projectId}/files/{fileId}/content | API key | Flip an existing file onto a replacement chunk you have already uploaded, and re-derive only what changed. - POST /v1/projects/{projectId}/files/reingest | API key | Re-run the derivation pass over named files without re-uploading their bytes. #### Watching changes docs: https://www.runlogai.com/docs/api/watching For the integration that must notice a change rather than poll for it: the SSE stream that reports the file set on every change, the ticket that opens it from a client that cannot set a header, and - part of an Enterprise agreement - the downstream-impact read that says what a changed file moves. - GET /v1/projects/{projectId}/files/stream | Session | Hold an SSE connection open and receive the file list on every change - how you watch an ingestion settle. - POST /v1/api/projects/{projectId}/files/stream-ticket | API key | Receive the short-lived ticket that opens the project's file stream without a header. #### API versions docs: https://www.runlogai.com/docs/api/api-versions For the client that must not break. Endpoint families carry three-part versions, a route or a version retires only on a published notice of at least 30 days, and the discovery read returns the whole projection - families, versions, schemas, contract rules, and every scheduled retirement - so a client can check what it was built against. - GET /v1/actions | API key | The versioned projection of every dispatchable action: families, versions, schemas, retirements. ### Execution agents on the memory: retrieval, sessions, artifacts, outbound, business. #### Retrieval docs: https://www.runlogai.com/docs/api/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/actions action=memory_query | API key | Rank the project's derived cells, entities, and artifacts against a query, packed to a budget. - POST /v1/api/actions action=code_context_pack | API key | Assemble the declarations, files, and relationships a stated task needs, packed to a token budget. - POST /v1/api/actions action=code_references | API key | Resolve every declaration and usage site matching a symbol query. ##### 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}/cells | API key | Page the project's derived cells, optionally scoped to one file, entity, or parent cell. - GET /v1/projects/{projectId}/entities | API key | Page the entities the project's cells resolve to - the nodes a code graph hangs off. - GET /v1/projects/{projectId}/factions | API key | Resolve faction ids to the file and the line span each was cut from - the place behind a citation. #### Agent sessions docs: https://www.runlogai.com/docs/api/queries 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-runs | API key | Start a conversation bound to one project. - POST /v1/api/agent-runs/{runId}/messages | API key | Append a user message to a run and get the answer, with the cells and declarations it cited. - GET /v1/agent-runs/{runId} | Session | Read the whole conversation - every message with its parts and citations. - POST /v1/api/agent-runs/{runId}/stream-ticket | API key | Receive the short-lived ticket that opens the run's stream without a header. - GET /v1/api/agent-runs/{runId}/stream | API key | Receive the answer as it forms, over server-sent events. ##### 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}/interrupt | Session | Stop the turn in flight - the cancel behind a user hitting stop. - POST /v1/agent-runs/{runId}/messages | Session | Ask for a change; the run answers with a plan and staged pending writes rather than applying anything. - GET /v1/agent-runs/{runId} | Session | Read the plan of record and the writes still staged for approval. - POST /v1/agent-runs/{runId}/pending-writes | Session | Decide each staged write by id; approved writes replay through the exact call the turn was refused. #### Artifacts docs: https://www.runlogai.com/docs/api/artifacts For the document your agent hands back. An artifact is generated from the project's own corpus - a report, a memo, a takeoff - and the reason to generate one here rather than in a chat window is that it comes back with its provenance attached. Every section names the cells it was derived from, and a section nothing in the corpus supports is returned as unsupported rather than written confidently. An artifact is ordinary corpus once it lands: retrievable, citable, and answerable for what it rests on - and promoting one to source evidence is the folder move documented here. - POST /v1/api/actions action=artifact_update | API key | Build a structured artifact from the project's corpus, with each section citing the cells it rests on. - PATCH /v1/projects/{projectId}/files/{fileId} | API key | Move a file across the `.runlog/Artifacts/` boundary to reclassify it as generated or as ingested source. #### Outbound docs: https://www.runlogai.com/docs/api/outbound For an agent that reaches a person. Reaching someone is three steps, and they are separate on purpose: something drafts, a human approves, and only then does anything leave. An external send is the one class of action the platform will not auto-fire - not from an agent, not from a website visitor, and not from an API key acting unattended - so the approval is a real gate rather than a confirmation dialog. - GET /v1/gtm/channel-inventory | API key | Read what each connected sending account may send today, and what it has spent. ##### People The roster, the board over it, and the day's queue: who sells, who is being reached, where each lead stands, and which drafts are waiting on a person. Part of an Enterprise agreement; the same abilities are open to an agent through the dispatch path above. - GET /v1/gtm/leads | Session | Page the project's leads, newest first, or one status column at a time. | ENTERPRISE - GET /v1/gtm/members | Session | Read the people with access to this project, each with what they chose and what they are carrying. | ENTERPRISE - GET /v1/gtm/outreach/review-queue | Session | Read the leads due a touch today, each as the approve-card a person answers. | ENTERPRISE - POST /v1/gtm/outreach/approve-bulk | Session | Approve one or several staged drafts so the outreach substrate sends them. | ENTERPRISE #### Business docs: https://www.runlogai.com/docs/api/business For an agent on the company itself. Everything the product does to a company's commercial position - stating what it sells and to whom, ranking what to do next, researching who it competes with, listening to what a market says - is an action with an API endpoint. What the application does through a screen, a key does through a route; there is no capability that exists only behind the UI. ##### Position What the company sells, to whom, and what the project concludes about it. Reads are derived on demand and ranked rather than stored as a verdict, every row citing the cells behind it; the writes steer what is derived next, without re-deriving from scratch. The routes the Business lens renders from are part of an Enterprise agreement; the dispatch below is not, so an agent states the same business model with no agreement anywhere in the picture. - PUT /v1/gtm/outreach-guidance | API key | Set the standing guidance every derived recommendation is held to. | ENTERPRISE ## Actions Every action below dispatches through one route: POST /v1/api/actions, with an API key on Authorization and the target project on X-Project-Id. Rate limit: 60 requests per minute per key. The key owner's access level on that project authorizes the call. Format: action_id | consequence | description ### artifact v1.0.0 - artifact_update | project-write | Generate or revise a structured artifact (report, memo, summary) built from the project's corpus. required input: purpose (string) contract: Generation is detached: a dispatch returns the running task to poll, and a dispatch naming an artifactId is refused on the API surface today - omit it to generate a new artifact. ### code v1.0.0 - code_context_pack | read | Return the slice of an ingested repository one coding task needs: the declarations ranked most relevant to the task, each as a signature citing its file and line, packed to a token budget. Complements an exact-string search rather than replacing it - it answers which code to read, not what it says. required input: task (string) contract: The response is signatures, never bodies: each declaration carries its header, file, and line span so you read the source yourself at the cited position. contract: The pack is ranked for precision, not coverage - it returns the few declarations a task turns on and stops well short of the budget when nothing else scores. A short pack is an answer, not a truncation; re-dispatch with a sharper task rather than a larger budget. contract: Seeds are subtracted from the answer: anything you name in seedSymbols or seedFiles steers the ranking and is never spent on your budget. - code_references | read | List the ingested code units whose symbol name matches a typed prefix, each as an addressable reference - a stable symbol id, the symbol name as its label, and its file path as its sublabel. This is the manual override for retrieval: the named way to point at code. contract: References are a bounded page ordered prefix-matches-first, then substring matches, each alphabetical, so the list stays stable as the user keeps typing. contract: The symbol id is stable across re-ingests - store that, never the label, when persisting what a user picked. ### document v1.0.0 - document_ingest | project-write | Pull a document into the project's corpus so its factions, cells, and entities feed the memory graph. required input: source (string) contract: Dispatch only starts the ingestion: the source ingests on a detached task the response names, so poll that task rather than treating the dispatch as a completed ingest. contract: One source, one page. Following links to the rest of a site is the research pass's job, so a whole site asked for here lands only what the named URL itself says. - document_verification | read | Extract the checkable claims from an ingested document and return a verdict ledger: per claim, whether the project's authority corpus SUPPORTS it (with the exact span), CONTRADICTS it (with the span), leaves it UNSUPPORTED (the corpus does not cover it - not a finding that it is false), or ABSTAINS when the evidence is too weak to call. Each row carries the exact locus it turned on. Scoped to the project the connection is bound to. required input: documentId (string) contract: The ledger is three-valued, never two: UNSUPPORTED means the corpus does not cover the claim - a statement about coverage, not a finding that the claim is false - and is distinct from CONTRADICTED, which means the corpus asserts the opposite and carries the contradicting span. contract: A verdict the evidence cannot support at the confidence threshold is ABSTAIN, never a forced call: a low-confidence resolve returns abstain rather than manufacturing a contradiction. contract: Every SUPPORTED or CONTRADICTED row carries the exact locus it turned on - the faction and its SourceLocation anchor - so the cited span opens the precise place in the corpus. An UNSUPPORTED or ABSTAIN row cites nothing by design. contract: The authority corpus is the project the connection is bound to, read as ground truth only - generated content is never cited as authority. ### file v1.0.0 - file_upload | project-write | Register a file the visitor already content-addressed and pushed to storage as chunks, staging it into the caller's session project so it ingests through the ordinary queue. required input: fileId (string), name (string), chunkIds (string[]) contract: The destination resolves server-side: the file always lands in the caller's session project, never in a project_id the visitor names. contract: One session per caller: the first upload mints the session; later calls reuse it, each advancing its idle watermark. contract: Upload URLs are minted by relay per chunk - a caller cannot name a source for relay to fetch. ### memory v1.1.0 - memory_query | read | Read the project's Runlog memory. With a query, returns the cells, entities, and artifacts ranked relevant to it, each carrying its ids as provenance; without one, lists cells (units of knowledge) or entities as a windowed dump. Scoped to the project the connection is bound to. contract: List reads are windowed: a dispatch returns one bounded page of items plus a nextCursor; pass the returned cursor to continue and treat an empty nextCursor as the final page - a single dispatch is never the whole memory. contract: Query reads are budgeted, not paged: the response packs the most relevant cells, entities, and artifacts into the requested token budget and stops - dispatch again with a refined query rather than a cursor. contract: A schema never replaces the query: the natural-language query is what decides WHICH knowledge is retrieved, and the schema only decides what SHAPE the answer takes over it. A dispatch carrying a schema and no query is refused rather than answered from an unranked corpus. contract: Tethers constrain, they do not rank: a fileIds or entities tether narrows the answer to what those files and entities back, so a tether naming nothing the project holds returns an empty result rather than a broader one. Leave both absent to retrieve across the whole project. contract: Every field of a structured answer is grounded in the returned cells - the rows stay on the response beside the structured object precisely so a caller can check each value against what it was derived from. ### object v1.0.0 - object_submit | project-write | Write one form submission as a typed record of the named-object type the site's owner bound to this action, validated against that type's declared fields. required input: data (object) contract: The type is the owner's binding (the action's form wrapper), never a field of the submission: a payload that does not fit the bound type's declared fields is refused, not stored. ### sku v1.0.0 - sku_create | project-write | Create a unit SKU row the storefront can sell: name, price, deliverable kind, scope, and turnaround. Price, scope and turnaround render from the catalog row at checkout time - never from page copy. required input: name (string), priceUsd (number), deliverableKind (string) contract: The SKU id is server-generated from the name; a page never names a price it did not read from the catalog. contract: The price, scope and turnaround render from the catalog row at checkout time, never from page copy. contract: Intake answers ride the purchase into the delivery run; oversize intake is refused at mint, never truncated. ### storefront v1.0.0 - storefront_create | project-write | Create a storefront host record bound to a project, so a published site has an identity tenant for visitor sign-up and a storefront for payments. required input: storefrontId (string), projectId (string) contract: The storefront is bound to one project; a project records the storefront it was created under. contract: The storefront provides an identity tenant for visitor sign-up and a payment surface for unit SKUs. ### unit v1.0.0 - unit_checkout | project-write | Mint one unit SKU's Stripe Checkout Session with the page's intake answers attached, for the visitor to pay on. Price, scope and turnaround come from the catalog row, never from the page. required input: skuId (string), returnTo (string) contract: The SKU id is the only thing the page names about the offer: the session's price, name and turnaround are read off the catalog row at mint, so a tampered id buys nothing. contract: Intake answers ride the purchase into the delivery run; oversize intake is refused at mint, never truncated. ### Versioning Endpoint families carry three-part versions; an endpoint retires only on a published notice of at least 30 days. - memory v1.0.0: memory v1.0.0 retires 2026-10-16 (notice published 2026-09-16); Atlas answers as an alias of the Runlog global until this version retires ## Workflows What the app does on a screen, composed from the routes above. docs: https://www.runlogai.com/docs/api/workflows ### Keep a directory read What `runlog link`, `sync` and `watch` do from a terminal, and the desktop app does behind Link, from your own process against a project your customer never sees. 1. GET /v1/api/workspace/describe | API key | Read the binding: which project this key opens and what it already holds. 2. GET /v1/api/workspace/manifest | API key | List what the project holds, so the diff is computed on your side. 3. POST /v1/api/workspace/quote | API key | Price the reading before anything uploads; free, and the number to show a person. 4. POST /v1/api/workspace/sync | API key | Push the files that changed; an in-flight file runs to completion. 5. POST /v1/api/workspace/reconcile | API key | Drop what is gone locally, so a deleted file stops being cited. Yields: A project that mirrors a directory, at declaration grain for code and by kind for everything else. ### Ask with citations The answer with source chips under it: what Ro gives in the thread and what Claude Code gets through the MCP server. 1. POST /v1/api/actions action=memory_query | API key | Search the spine with a question; the answer carries the cell ids it rested on. 2. POST /v1/api/actions action=code_context_pack | API key | For code: a pack of declarations sized to a token budget, each with its span. 3. GET /v1/projects/{projectId}/cells | API key | Open a cited cell to render the source beside the claim. 4. POST /v1/api/agent-runs | API key | For a conversation rather than one read: open a run. 5. POST /v1/api/agent-runs/{runId}/messages | API key | Ask; the run decomposes an open-ended goal into steps. 6. POST /v1/api/agent-runs/{runId}/stream-ticket | API key | Mint the ticket a browser needs to read the stream without holding the key. 7. GET /v1/api/agent-runs/{runId}/stream | API key | Stream the answer as it lands, citations included. Yields: Every claim with the ids behind it, so your product can render a receipt rather than a paragraph. ### Change without re-reading What the watcher does: a file that changes re-reads only its delta, and the answer changes with it. 1. PUT /v1/projects/{projectId}/files/{fileId}/content | API key | Replace one file’s content; only the delta re-ingests. 2. POST /v1/projects/{projectId}/files/reingest | API key | Re-read a set of files on purpose: a schema moved, a parser improved. 3. POST /v1/api/projects/{projectId}/files/stream-ticket | API key | Mint a ticket so a page can subscribe to file changes without the key. 4. GET /v1/projects/{projectId}/files/stream | Session | Subscribe: per-file status as it moves, the same feed the app’s progress bars read. Yields: A project that stays current under your customer’s edits, with progress you can show. ### Deliver a cited artifact The page Ro drafts with a citation on every line, produced for your own surface. 1. POST /v1/api/actions action=artifact_update | API key | Generate an artifact from a brief; provenance rides every claim. 2. GET /v1/projects/{projectId}/files/{fileId}/entities | API key | Read what a source resolved to: the people, terms and components a claim names. 3. PATCH /v1/projects/{projectId}/files/{fileId} | API key | Promote the artifact to a file the project cites from now on, or demote it. Yields: A deliverable whose every line opens the source it came from. ### Provision a customer Runlog under your brand: one project per customer, one narrow key per service, nothing shared between them. 1. POST /v1/projects | API key | Create the project, the memory boundary every later call is scoped to. 2. POST /v1/mcp-keys | API key | Mint a project-bound key for the service that will read and write it. 3. POST /v1/projects/{projectId}/files/zip | API key | Bring the first corpus in as a zip; a codebase or a folder of documents alike. 4. GET /v1/projects/{projectId}/files | API key | Confirm what landed and watch it move to done. 5. POST /v1/api/actions action=memory_query | API key | The first cited answer, from your product, on your customer’s data. Yields: An isolated memory per customer that your product reads through one key; the white-label model’s unit of work.