String API
Feed Builder API

Builds

Start, list, watch, steer and finish a feed build.

Starting a build

curl -X POST "$FEEDBUILDER_API/v1/builds" \
  -H "Authorization: Bearer $STRING_FEED_BUILDER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 11111111-2222-3333-4444-555555555555" \
  -d '{
    "query": "a feed of laptop listings from example.com",
    "mode": "supervised",
    "publication": { "policy": "manual" }
  }'
FieldRequiredNotes
queryyesWhat to build, in prose. At most 4000 characters, counted in runes.
modeyesNo default. supervised is the only value served — see below.
publication.policynomanual, which is what a supervised build already does. auto returns 501.

201 returns the id and nothing else:

{ "object": "build", "id": "bld_01J8..." }

mode has no default

Choosing between "this build may stop and ask you things" and "it never will" changes what you have to build on your side, so the API does not pick for you.

modeServed today
supervisedyes
autonomousno — 501
fastno — 501

An unrecognised mode is a 400. A recognised but unserved one is a 501, because your request is correct and the missing capability is ours. Limits explains why.

Lengths are counted in runes

query 4000, message text 2000, cancel reason 1000 — all counted in runes, not bytes, so a request written in a non-Latin script is not refused at a third of the length of the same request in English.

A request body over 1 MiB is a 413.

Idempotency

Idempotency-Key takes a UUID and maps straight onto the build id the service dedupes on. Retrying a create with the same key returns the existing build rather than starting a second one. There is no separate idempotency store, so there is nothing that can disagree with the build index about whether your key has been seen.

Reusing a key against a build that exists in an incompatible state is a 409.

Listing builds

curl "$FEEDBUILDER_API/v1/builds?page_size=25" \
  -H "Authorization: Bearer $STRING_FEED_BUILDER_KEY"

Newest first, and only your organization's builds. page_size is 1–100; asking for more is refused, not clamped, so a caller asking for 10 000 learns they will not get them instead of silently receiving 50. Pass the response's next_page_token back as page_token for the next page.

{
  "object": "list",
  "data": [{ "object": "build", "id": "bld_…", "status": "awaiting_input", "query": "…", "pending_gate_id": "gate_…", "pending_gate_kind": "schema_review" }],
  "total_size": 1,
  "next_page_token": "…"
}

Getting a build

GET /v1/builds/{id} returns the full build: status, progress, pending_gate, gate_history, messages, activity, report, harvest, cost_usd and a monotonic version.

status is one of running, completed, errored, cancelled, awaiting_input, unknown. awaiting_input means the build is parked on a gate waiting for you — nothing moves until you answer it.

error on a build is customer-facing copy: it is the same string the run-finished email carries, not an operator diagnostic.

Every list and map is `[]` or `{}` when empty

No response from this API contains a JSON null, at any depth. You can iterate candidates, questions, sample_rows, fields and answers without a presence check.

Auto-proceed

PATCH /v1/builds/{id} toggles whether the build advances past gates it can answer for itself.

{ "auto_proceed": true }

`auto_proceed` must be sent explicitly

Omitting the key is a 400 (auto_proceed_required), never a silent false. A silent false would park the build at its next gate, which looks like a hang rather than a setting you changed. null earns the same 400.

Sending a message

POST /v1/builds/{id}/messages puts free text into a running build — a correction, a hint, an extra constraint.

{ "text": "only listings under $2000", "interrupt": false }

interrupt: true aborts the builder's turn in flight instead of queueing behind it. Omitted means queue, which is the non-destructive default. Text is at most 2000 runes.

Sending a message to a build that has already finished is a 409, not a 400: nothing is wrong with your request, the build is in the wrong state for it.

Cancelling a build

POST /v1/builds/{id}/cancel, optionally with {"reason": "…"} (at most 1000 runes).

Deleting a build

DELETE /v1/builds/{id} soft-deletes it: it stops appearing in listings. 204 on success.

Sample rows

GET /v1/builds/{id}/sample returns the published field schema and real sample rows for a build that produced them.

{
  "object": "sample",
  "build_id": "bld_…",
  "feed_name": "example_laptops",
  "fields": [{ "key": "title", "type": "string", "required": true, "repeated": false }],
  "sample_rows": [["ThinkPad X1", "1899.00", "https://example.com/…"]],
  "rows_written": 4821
}

Rows are arrays, and a sample is not the dataset

sample_rows are arrays positionally aligned with fields, never objects keyed by column name — an object would silently drop a duplicate or reordered column instead of preserving the positions the producer promised.

And this endpoint is a sample, not an export. There is no bulk data download on this API; the built feed is delivered through the subscription the publish-review gate registers. See Limits.