Quickstart
Follow an example build from POST to published feed, with requests and responses.
This example follows a build that collects job vacancies, from the first request through publication. Responses are shortened to show the fields each step uses.
1. Start the 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 current job vacancies from the UK government Find a job service at https://findajob.dwp.gov.uk/ - capture job title, employer, location, salary, posting date, closing date, contract type and the listing URL."
}'{ "id": "680f1af1-367e-476a-9139-160e61645530", "object": "build" }Keep that id — it is the only handle you get, and every later step uses it.
2. Watch it
Two ways. Poll GET /v1/builds/{id}, or subscribe to the stream — same information, and the stream is
the right choice if a person is watching.
curl -N "https://feedbuilder.usestring.ai/v1/builds/{id}/events" \
-H "Authorization: Bearer $STRING_FEED_BUILDER_KEY" \
-H "Accept: text/event-stream"An example stream opening, from a different build than the one in the other steps, with shortened data:
lines:
id: 3
event: build.state
data: {"id":"2c9c0dc0-…","object":"build","version":3,"status":"running","query":"A feed of the current job openings …"}
id: 3
event: build.activity
data: {"at":"2026-08-28T15:28:21.481314356Z","type":"tool_call","tool_name":"Bash"}
id: 5
event: build.progress
data: {"id":"2c9c0dc0-…","version":5,"status":"running","progress":{"summary":"Working"}}
id: 5
event: build.activity
data: {"at":"2026-08-28T15:28:25.838602599Z","type":"narration","text":"The request clearly names its source …"}
id: 8
event: build.gate.raised
data: {"id":"3970a623-…","object":"gate","kind":"initial_query","prompt":"About to build this feed — confirm before I start","raised_at":"2026-08-28T15:28:28.583474626Z","expires_at":"2026-08-28T15:30:28.583478325Z","options":{…}}
: keep-alive| Frame | What it is |
|---|---|
build.state | A whole build object — the same one GET /v1/builds/{id} returns. Always the first frame. Render from it; everything after is an update. |
build.progress | status and a one-line progress.summary. Liveness, not history. |
build.activity | One step: a tool_call with its tool_name, or a narration Composer wrote. |
build.gate.raised | A gate opened. This is the signal to put a person on it. |
: keep-alive | An SSE comment every 15 seconds, so a silent build does not look like a dead connection. Ignorable by the protocol. |
id: carries the build's version, and it does not increment once per frame — several frames share a
version, and versions are skipped. Treat it as a checkpoint, not a sequence number.
Do not block waiting for `build.state`
On a reconnect, Last-Event-ID suppresses frames you already have rather than replaying ones you
missed — so if nothing changed while you were away, the stream opens silent and no snapshot arrives.
Render from what you have, or from GET /v1/builds/{id}, and let the frame update you when it comes.
3. Answer the first gate
When Composer needs an answer, status becomes awaiting_input:
{
"status": "awaiting_input",
"version": 9,
"pending_gate": {
"id": "6776d1bc-8b32-4e40-acc2-95169ef2d580",
"kind": "initial_query",
"prompt": "About to build this feed - please confirm scope before I start.",
"options": {
"refined_request": "Build a job_postings feed that scrapes current job vacancies listed on the UK government's Find a Job service (findajob.dwp.gov.uk). Each row will capture: job title, employer/company name, location, salary (as displayed), posting date, closing date, contract type, and the listing URL. …"
}
}
}This is Composer restating your request before spending anything. Read refined_request — it is
your last cheap chance to correct the scope.
curl -X POST "https://feedbuilder.usestring.ai/v1/builds/680f1af1-…/gates/6776d1bc-…" \
-H "Authorization: Bearer $STRING_FEED_BUILDER_KEY" \
-H "Content-Type: application/json" \
-d '{"initial_query": "Confirmed. Build exactly that scope."}'204 No Content, and the build resumes.
Not every gate stops for you
auto_proceed is on by default, and Composer answers the gates it can automatically. An answered
schema_review gate shows up in gate_history as {"kind": "schema_review", "answer": {"schema": {"approve": true}}}.
Read gate_history to see what was decided on your behalf, and turn it off with
PATCH /v1/builds/{id} if you want every gate.
4. Approve publication
The publish_review gate asks you to approve the dataset (prompt shortened, and fields and the row cut to
three of the build's eight columns):
{
"id": "3a1e9d90-8012-47b6-b90b-ff9ed135f940",
"kind": "publish_review",
"prompt": "Final review: feed job_postings.dwp_findajob_vacancies validated with 5 rows … Approve to publish, or reject with notes to withhold it.",
"options": {
"feed_name": "job_postings.dwp_findajob_vacancies",
"source_url": "https://findajob.dwp.gov.uk/",
"rows_written": 5,
"fields": [
{ "key": "job_title", "type": "string", "required": true, "repeated": false, "description": "Job title as listed" },
{ "key": "employer", "type": "string", "required": true, "repeated": false },
{ "key": "listing_url", "type": "string", "required": true, "repeated": false }
],
"sample_rows": [
["Example job title", "Example employer", "https://example.com/jobs/123"]
]
}
}sample_rows are positionally aligned with fields and contain data collected by the build.
curl -X POST "https://feedbuilder.usestring.ai/v1/builds/680f1af1-…/gates/3a1e9d90-…" \
-H "Authorization: Bearer $STRING_FEED_BUILDER_KEY" \
-H "Content-Type: application/json" \
-d '{"publish": {"approve": true, "notes": "Approved."}}'204. approve must be present — omitting it is 400 approve_required, never a decline.
5. Confirm it landed
{
"status": "completed",
"version": 349,
"report": {
"feed_name": "job_postings.dwp_findajob_vacancies",
"source_url": "https://findajob.dwp.gov.uk/",
"rows_written": 5
}
}completed with no error means the feed is registered and its delivery subscription exists. A build
that reaches a validated dataset but fails to register reports errored with the reason in error.
`version` keeps moving after `completed`
A build’s report can be updated after completion. completed_at and status do not change, so a
terminal status does not mean the build object is final. Poll or stay
subscribed if you read report after completion.
6. Read the schema back
curl "https://feedbuilder.usestring.ai/v1/builds/680f1af1-…/sample" \
-H "Authorization: Bearer $STRING_FEED_BUILDER_KEY"{
"object": "sample",
"build_id": "680f1af1-367e-476a-9139-160e61645530",
"feed_name": "job_postings.dwp_findajob_vacancies",
"fields": [ … 8 fields … ],
"sample_rows": [ … 5 rows … ],
"rows_written": 5
}This is a sample, not an export. Once the feed is published, download its files with Feed data.
What to build on your side
The two things this walkthrough shows that a client has to handle:
- A gate can appear at any time. It waits for your answer until its
expires_at, if it has one, and then Composer answers it for you. Watch forstatus == "awaiting_input"and route it to a person. Nothing calls you back — see Noticing a gate. - A build is a long-running job. Treat
POST /v1/buildsas the start of a long job, keep the id, and poll or stream. Do not hold a request open waiting for it.