Builds
Start, list, watch, steer and finish a feed build.
Starting a build
curl -X POST "https://feedbuilder.usestring.ai/v1/builds" \
-H "Authorization: Bearer $STRING_FEED_BUILDER_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "a feed of laptop listings from example.com"
}'| Field | Required | Notes |
|---|---|---|
query | yes | What to build, in prose. At most 4000 characters, counted in runes. |
201 returns the id and nothing else:
{ "object": "build", "id": "4c1d8a76-0f2b-4e93-9a5d-6b7c8e0f1a23" }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.
Keep the id — a create is not repeatable
Each POST /v1/builds starts a new build and bills for it. The id in the 201 is the only handle you get, so
store it before you do anything else; a request that times out after the server accepted it leaves a build
running that you can still find in GET /v1/builds.
Listing builds
curl "https://feedbuilder.usestring.ai/v1/builds?page_size=25" \
-H "Authorization: Bearer $STRING_FEED_BUILDER_KEY"Newest first, and only your organization's builds. page_size is at most 100; a larger value is refused, not
clamped. Omitting it — or sending 0 — takes the default. Pass the response's next_page_token back as
page_token for the next page.
{
"object": "list",
"data": [{ "object": "build", "id": "7b2e5f10-3c94-4a8d-8e61-2f0a9c3d5b74", "status": "awaiting_input", "query": "…", "pending_gate_id": "e0a4d9c2-8b31-4f57-a6d0-3c9e1b7f2a85", "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 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.
`awaiting_input` and `pending_gate` are the same fact
They are guaranteed to agree, so either one answers "is this waiting on me?" and you never have to check both:
status: "awaiting_input"⟺pending_gateis present (andpending_gate_idon a list row).- A terminal build —
completed,errored,cancelled— never reports a pending gate, even if it was holding one when it ended. There is nothing left to answer, and answering would only earn a409.
A run that ended because we had to step in reports errored, not awaiting_input. If a build is waiting on
us rather than on you, it will never say it is waiting on you.
error on a build is customer-facing copy: it is the same string the run-finished email carries, not an operator
diagnostic.
No response contains a JSON `null` — but a collection can be absent
No response from this API contains a JSON null, at any depth, so nothing you read needs a null check.
Presence is a separate question. candidates, questions, a choice gate's choices, and the fields and
sample_rows of GET /v1/builds/{id}/sample are always written, empty or not. Others are omitted when empty
rather than sent as [] — gate_history, messages and activity on the build, a question's choices, and
examples and edits. Treat an absent collection as an empty one; do not read .length off it unguarded.
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 Composer'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": "a8d3f612-5e07-4b9c-81fa-72d4e6035c9b",
"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.
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.