String API
Feed Builder API

Walkthrough

One real build from POST to published feed, with the actual requests and responses.

Every request and response below is from a single real build — 680f1af1-367e-476a-9139-160e61645530, which produced the feed job_postings.dwp_findajob_vacancies. Ids, timings and payloads are as they came back, trimmed only where a field repeats.

It took 91 minutes and cost $4.17, and it answered three gates on the way.

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" \
  -H "Idempotency-Key: 680f1af1-367e-476a-9139-160e61645530" \
  -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."
  }'
201 Created
{ "id": "680f1af1-367e-476a-9139-160e61645530", "object": "build" }

The id is the Idempotency-Key you sent. Retrying this exact request returns 409 already_exists rather than a second 201 — see Idempotency.

2. Wait for the first gate

Poll GET /v1/builds/{id} or subscribe to the stream. Roughly a minute in, status became awaiting_input:

GET /v1/builds/680f1af1-…
{
  "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 the builder restating your request before spending anything. Read refined_request — it is your last cheap chance to correct the scope.

3. Answer it

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

This build raised three gates, but only two reached the caller. schema_review was answered automatically — auto_proceed is on by default, and the builder answers the gates it can. It 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

About 90 minutes later — the crawl alone ran 10 minutes and read 244 rows — the build parked on its last gate at version 346:

pending_gate
{
  "id": "3a1e9d90-8012-47b6-b90b-ff9ed135f940",
  "kind": "publish_review",
  "prompt": "Final review: feed job_postings.dwp_findajob_vacancies validated with 5 rows (sample below, verified server-side). Approve to publish — the feeds PR opens and the feed is registered — 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": [
      ["Locum SHO - (ENT, Surgery, Medicine, Psychiatry & T&O)", "Medecho LTD", "Chichester", "£35 - £45", "27 Aug 2026", "26 Sept 2026", "Contract", "https://www.jobs.service.gov.uk/jobs/6a9077df012f773fd8f48738"]
    ]
  }
}

sample_rows are positionally aligned with fields — real rows from a live run, not a schema preview.

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

GET /v1/builds/680f1af1-… after approval
{
  "status": "completed",
  "version": 349,
  "cost_usd": 4.168581349999999,
  "report": {
    "feed_name": "job_postings.dwp_findajob_vacancies",
    "source_url": "https://findajob.dwp.gov.uk/",
    "rows_written": 5
  },
  "harvest": { "status": "complete", "rows": 244, "truncated": true }
}

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.

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. The dataset itself arrives through the delivery subscription the publish-review gate registered — see Limits.

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 and blocks until answered. Watch for status == "awaiting_input" and route it to a person. Nothing calls you back — see Webhooks.
  • A build runs for tens of minutes. Treat POST /v1/builds as the start of a long job, keep the id, and poll or stream. Do not hold a request open waiting for it.