Skip to content
Établi

For developers and agents

Établi API docs

Physical work from one API call. Send the work in plain English; Établi plans it into legs, one for each kind of workshop it needs, and independent workshops bid on each. Start with no key: one call returns the plan, and one more drafts it for the person to claim and publish.

Base URL
https://api.etabli.io
No key needed
POST /v1/quotes/preview plans a request; POST /v1/projects drafts it to claim
MCP server
https://api.etabli.io/mcp (install)
For agents
llms.txt · reference.md · OpenAPI

Quickstart: a plan, with no key

Send the work in plain English: what it is, how many, where it goes, by when, and the most the person would pay. Établi returns the plan: the legs, each with its spec and the questions still open. No key, no account, and nothing is ordered or sent to workshops.

curl -sX POST https://api.etabli.io/v1/quotes/preview \  -H "Content-Type: application/json" \  -d '{"request":"Print 3,000 mailer boxes with our logo in one color, split across our warehouses in New Jersey, Texas and Nevada, within 4 weeks. Up to $1.20 a box."}'
Planning takes up to a minute: allow a long timeout. Previews are rate-limited per IP address.

What comes back, from a real run of that request, abridged:

{  "title": "3,000 one-color logo mailer boxes, 3 warehouses",  "summary": "Produce 3,000 custom corrugated mailer boxes printed with the buyer's logo in one color, delivered split across warehouses in New Jersey, Texas and Nevada within 4 weeks, at up to $1.20 per box.",  "legs": [    {      "key": "boxes",      "title": "3,000 one-color printed mailer boxes",      "role": "Manufacture and print the logo mailer boxes and deliver them split across the buyer's three warehouses.",      "spec": {        "price": {          "ceilingCents": 120,          "per": "unit",          "includes": [            "boxes and board",            "one-color printing",            "printing plates, cutting die and setup",            "digital proof",            "packing flat on pallets or in cartons",            "freight to all three warehouses"          ],          "excludes": []        },        "deadline": {          "kind": "fixed_date",          "businessDays": null,          "date": "2026-11-06"        },        "…": "work, output, proof, where, rules"      },      "questions": [        {          "field": "work",          "text": "What inside dimensions, board grade/flute and board color should the boxes be?",          "suggestedAnswer": "9 x 6 x 3 in inside, 32 ECT E-flute, kraft board."        }      ],      "inputsFrom": [],      "sourcing": {        "countries": [          "US"        ],        "…": "lookFor, searchTerms"      }    }  ],  "engine": "claude:claude-opus-5-5"}

Each leg's spec is the contract in eight fields: inputs, work, output, price (the most the buyer pays, in ceilingCents), deadline, proof, where and rules. A plan has no price of Établi's own: prices come from workshops' bids. The packaging guide shows this plan in full.

Answer the open questions

Ask the person each question; each comes with a suggested answer they can take. Then preview again with answers: each with its leg's key as leg, and the question's field and text exactly as returned.

curl -sX POST https://api.etabli.io/v1/quotes/preview \  -H "Content-Type: application/json" \  -d '{    "request": "Print 3,000 mailer boxes with our logo in one color, split across our warehouses in New Jersey, Texas and Nevada, within 4 weeks. Up to $1.20 a box.",    "answers": [      { "leg": "boxes", "field": "work", "text": "What inside dimensions, board grade/flute and board color should the boxes be?", "answer": "9 x 6 x 3 in inside, 32 ECT E-flute, kraft board." },      { "leg": "boxes", "field": "rules.notes", "text": "Should the logo print on the outside only, and in which Pantone color?", "answer": "Outside only, lid top, Pantone Black C." }    ]  }'

Draft and claim: the person publishes

When the person wants workshops to bid, draft the project: POST /v1/projects with no key, the same request and answers. The draft belongs to nobody until the person claims it on etabli.io, where they check the plan and its price ceilings, answer anything still open, and press Claim and publish. Nothing reaches a workshop, and nothing is charged, until they do.

curl -sX POST https://api.etabli.io/v1/projects \  -H "Content-Type: application/json" \  -H "Idempotency-Key: $(uuidgen)" \  -d '{    "request": "Print 3,000 mailer boxes with our logo in one color, split across our warehouses in New Jersey, Texas and Nevada, within 4 weeks. Up to $1.20 a box.",    "answers": [      { "leg": "boxes", "field": "work", "text": "What inside dimensions, board grade/flute and board color should the boxes be?", "answer": "9 x 6 x 3 in inside, 32 ECT E-flute, kraft board." },      { "leg": "boxes", "field": "rules.notes", "text": "Should the logo print on the outside only, and in which Pantone color?", "answer": "Outside only, lid top, Pantone Black C." }    ],    "agent": { "name": "your-agent" }  }'
Each draft needs its own Idempotency-Key, any unique string: a retry with the same key within 24 hours returns the same draft, project key and code. 10 drafts an hour and 30 a day per address. Send contact (the person's name and email, to prefill the claim page) only if they agree.

It responds 201 with the project, the project key and the claim link. Abridged:

{  "project": {    "id": "prj_01k7…",    "buyerId": null,    "status": "ready",    "claim": {      "status": "unclaimed",      "expiresAt": "… (7 days)",      "claimedAt": null    },    "draftedBy": {      "name": "your-agent",      "version": null    },    "next": {      "action": "show_claim_link",      "message": "Show the person this line: To check and publish your project, open https://etabli.io/claim#KQ7M-4TRD (or go to etabli.io/claim and enter KQ7M-4TRD) within 15 minutes. Nothing goes to workshops until they do.",      "url": "https://etabli.io/claim#KQ7M-4TRD",      "checkAfter": "… (10 minutes)"    },    "legs": [      {        "key": "boxes",        "tenderId": "tnd_…1",        "status": "ready",        "…": "spec, questions"      }    ]  },  "projectKey": "etb_live_prj_…",  "claim": {    "url": "https://etabli.io/claim#KQ7M-4TRD",    "code": "KQ7M-4TRD",    "codeExpiresAt": "… (15 minutes)"  }}

Show the person `project.next.message` word for word, on its own line. It carries the claim link (https://etabli.io/claim#KQ7M-4TRD): the code rides in the fragment, lasts 15 minutes and works once. Only the person can claim: the API refuses a claim sent with a key.

The project key

  • projectKey (etb_live_prj_…) is shown once. Keep it out of code and chats: in an environment variable, or a git-ignored file.
  • With it, as Authorization: Bearer: GET /v1/projects/{id} and its events; answering its legs' questions before the claim (POST /v1/tenders/{tenderId}/answers); a fresh claim code; discarding the draft (DELETE /v1/projects/{id}). After the claim: its offers, and answering workshops' questions if the person allowed it.
  • Publishing, awarding and ordering are the person's. Before the claim they answer 403 claim_required, with the claim page in details.claimUrl; after it, Établi contacts the person to confirm the offers they choose and arrange payment.
  • The person can revoke the key on their status page. From then on it answers 401.

Claim codes

A code is eight letters and digits, like KQ7M-4TRD, read in any case, with or without the dash. It lasts 15 minutes, or up to 24 hours if you ask (ttlMinutes, for a run nobody is watching), and a new code voids the old one. The person can also type it at etabli.io/claim. Five wrong codes from one address lock it out for 15 minutes. A draft nobody claims lapses after 7 days.

What to do next, and when to look again

Every project response carries next: {action, message, url, checkAfter}. message is one or two sentences to repeat to the person. While a claim or bids are pending, the Retry-After header (in seconds) mirrors checkAfter: look again then, not sooner. Bids take days, so tell the person, and say they'll get a status link to bookmark when they claim.

`next.action`Where it standsWhat to do
show_claim_linkDrafted, not yet openedShow the person the message; if the code lapsed, get a fresh one
answer_questionsA leg needs answers before it can go out to bidAsk the person, or show them the claim link: they can answer there
wait_for_claimThe person has the claim page openWait: nothing goes out until they publish
wait_for_bidsPublished; bidding is openLook again after checkAfter
review_offersBidding has closedTell the person: Établi contacts them about the offers
awardedEvery leg has a contractÉtabli arranges payment and the first orders with the person
expired, discardedThe draft lapsed, or was discardedDraft again if the person still wants it

Following it

# Where it stands: `next` says what to do; Retry-After says when to look again (-i shows it).curl -si https://api.etabli.io/v1/projects/$PROJECT_ID -H "Authorization: Bearer $ETABLI_PROJECT_KEY" # What's happened since the last event you saw (oldest first).curl -s "https://api.etabli.io/v1/projects/$PROJECT_ID/events?after=$LAST_EVENT_ID" \  -H "Authorization: Bearer $ETABLI_PROJECT_KEY" # The code lapsed before the person opened it: a fresh one (the old one stops working).curl -sX POST https://api.etabli.io/v1/projects/$PROJECT_ID/claim-codes \  -H "Authorization: Bearer $ETABLI_PROJECT_KEY" -H "Content-Type: application/json" \  -d '{"ttlMinutes": 60}'

Events are in Établi's own words (drafted, claimed, published, a bid, a workshop's question, a contract), oldest first: pass the last id you've seen as after. GET /v1/projects/{id}/offers opens to the project key once the person has claimed.

Firm prices by email, instead

Without a claim page: send the same request and answers with the person's name and email to POST /v1/quote-requests. Établi puts each leg out to bid and emails them the workshops' offers. Nothing is ordered until they accept one.

Ask before you send it. This shares the person's name and email with Établi. Call it only once they have said yes.

# Only after the person has said yes to sharing their name and email.curl -sX POST https://api.etabli.io/v1/quote-requests \  -H "Content-Type: application/json" \  -d '{    "request": "Print 3,000 mailer boxes with our logo in one color, split across our warehouses in New Jersey, Texas and Nevada, within 4 weeks. Up to $1.20 a box.",    "answers": [      { "leg": "boxes", "field": "work", "text": "What inside dimensions, board grade/flute and board color should the boxes be?", "answer": "9 x 6 x 3 in inside, 32 ECT E-flute, kraft board." },      { "leg": "boxes", "field": "rules.notes", "text": "Should the logo print on the outside only, and in which Pantone color?", "answer": "Outside only, lid top, Pantone Black C." }    ],    "name": "Their name",    "email": "them@example.com"  }'
It responds 201 with {ok, id, plan}: id is the request's reference, to give the person.

If no workshop on Établi fits a leg yet, Établi finds suitable ones and asks them to bid, which can take a few days. A person at Établi approves every message before it goes out.

Authentication

  • The preview, drafts (POST /v1/projects without a key), firm-price requests, support requests and workshop applications need no key. They are rate-limited per IP address; a 429 says how long to wait in Retry-After.
  • A draft's project key (etb_live_prj_…) works on that one project, and so does the person's owner token (etb_live_own_…, in their status link). Both go in Authorization: Bearer.
  • Everything else takes an account's key: Authorization: Bearer etb_live_…, for a buyer, a workshop or an operator.
  • An agent working for one person needs no key: it drafts, and the person claims. Buyer keys are for companies that run the whole loop through the API themselves: request API access.
  • A key is shown once. Keep it out of code and prompts: read it from an environment variable such as ETABLI_API_KEY.

With a key: from a request to orders

The same request, as a project you run yourself:

# 1. Plan the request into legs; each leg is a tender with a spec and questions.curl -sX POST https://api.etabli.io/v1/projects \  -H "Authorization: Bearer $ETABLI_API_KEY" \  -H "Content-Type: application/json" \  -d '{"request":"Print 3,000 mailer boxes with our logo in one color, split across our warehouses in New Jersey, Texas and Nevada, within 4 weeks. Up to $1.20 a box."}' # 2. Answer each leg's questions (or take the suggested answers).curl -sX POST https://api.etabli.io/v1/tenders/$TENDER_ID/answers \  -H "Authorization: Bearer $ETABLI_API_KEY" -H "Content-Type: application/json" \  -d '{"acceptSuggested": true}' # 3. Publish: every ready leg goes out to bid.curl -sX POST https://api.etabli.io/v1/projects/$PROJECT_ID/publish -H "Authorization: Bearer $ETABLI_API_KEY" # 4. Offers per leg, all-in (Établi's fee included).curl -s https://api.etabli.io/v1/projects/$PROJECT_ID/offers -H "Authorization: Bearer $ETABLI_API_KEY" # 5. Award: one contract per leg. Ask the person first: this commits real money.curl -sX POST https://api.etabli.io/v1/projects/$PROJECT_ID/award \  -H "Authorization: Bearer $ETABLI_API_KEY" -H "Content-Type: application/json" -d '{}'

Place orders

Each unit of work is one call: a customer's purchase, a recipient's parcel, a batch to one address, or a lot for a leg that supplies another (with no address, it ships to the next leg's workshop). Send an Idempotency-Key, so a retry returns the first order instead of placing a second.

# One customer's order, shipped to them.curl -sX POST https://api.etabli.io/v1/contracts/$CONTRACT_ID/orders \  -H "Authorization: Bearer $ETABLI_API_KEY" \  -H "Content-Type: application/json" \  -H "Idempotency-Key: order-1042" \  -d '{    "quantity": 1,    "reference": "order-1042",    "address": { "name": "Sam Rivera", "line1": "100 Main St", "city": "Springfield", "region": "IL", "postalCode": "62701" }  }'

Then GET /v1/orders/{id} for its status and proof (photos, a tracking number), and GET /v1/contracts/{id}/statement?week= for a week's orders and charges.

Awarding a contract and placing an order commit real money. An agent confirms each with the person first.

Endpoints

The ones a buyer's agent uses. Every endpoint, with its parameters, body and responses, is in the API reference (markdown, generated from the OpenAPI document), and in the interactive reference.

EndpointKeyDoes
POST /v1/quotes/previewnoneThe plan: legs, each with its spec and open questions. Nothing is ordered.
POST /v1/quote-requestsnoneFirm prices by email: the request, answers, name and email. Ask the person first.
POST /v1/projectsnone, or buyerWithout a key, a draft for the person to claim: a project key and a claim link. With a buyer key, a project you run yourself.
GET /v1/projects/{id}project key, owner token or buyerA project, with each leg's status, questions and contract, and next.
GET /v1/projects/{id}/eventsproject key, owner token or buyerWhat happened, oldest first; after for what's new.
POST /v1/projects/{id}/claim-codesproject keyA fresh claim code and line to show; the old code stops working.
DELETE /v1/projects/{id}project keyDiscard a draft the person doesn't want.
POST /v1/tenders/{id}/answersproject key or buyerAnswer a leg's questions, or acceptSuggested. With a project key, before the claim.
POST /v1/projects/{id}/publishbuyerPut every ready leg out to bid.
GET /v1/projects/{id}/offersbuyer, owner token, or project key once claimedOffers per leg at all-in prices, and the recommended ones added up per finished unit.
POST /v1/projects/{id}/awardbuyerOne contract per leg; an empty body takes the recommendation.
POST /v1/tendersbuyerOne job for one workshop: a single tender instead of a project.
POST /v1/contracts/{id}/ordersbuyerPlace an order: per customer, recipient, batch or lot. Takes Idempotency-Key.
GET /v1/orders/{id}buyerAn order and its proof.
POST /v1/orders/{id}/cancelbuyerCancel an order before it ships or is done.
GET /v1/contracts/{id}/statementbuyerA week's orders, charges and fees.
GET /v1/meanyThe account behind the key.
POST /v1/support-requestsnoneWrite to Établi; there is no email address.

Workshops bid with their own key or through the bid link Établi sends them, and operators run sourcing and outreach; their endpoints are in the reference too.

Conventions

  • JSON in and out, with camelCase fields. Times are ISO 8601; responses are in UTC.
  • IDs are prefixed: prj_ (project), tnd_ (tender: one per leg), bid_, ctr_ (contract), ord_ (order).
  • Money is integer US cents: buyerUnitPriceCents: 1250 is $12.50. Prices on offers are all-in: Établi's fee is included.
  • Lists come newest first. Resources you can't see answer 404, never 403.
  • Errors are {"error": {"code", "message", "details"}}, with the status:
StatusCodeWhen
400validationThe body, query or a header fails its schema, or a rule (publishing with open questions)
401unauthorizedNo key where one is needed, or an unknown or revoked key
403forbiddenThe wrong kind of account or key for this action
403claim_requiredA draft's project key tried to publish, award or order: the person claims it first (details.claimUrl)
404not_foundIt doesn't exist, or it isn't yours
409conflict, invalid_transition, no_eligible_bidsNot possible in its current state
422policy_violation, above_ceilingWork Établi won't take; a bid above the buyer's ceiling
429rate_limitedToo many requests from one address: wait for Retry-After
503spec_engine_unavailablePlanning is unavailable for a moment: retry

MCP

Établi's tools for assistants and agents, over Streamable HTTP. The MCP page has every client and every tool.

EndpointKeyTools
https://api.etabli.io/mcpnonehow_etabli_works, preview_quote, create_project (a draft for the person to claim), get_project, request_firm_prices; with a buyer key, read-only status. Nothing that spends money.
https://api.etabli.io/mcp/fullbuyerThe whole loop: post a project, publish, award, place orders.
  • Claude Code

    claude mcp add --transport http etabli https://api.etabli.io/mcp
  • Codex

    codex mcp add etabli --url https://api.etabli.io/mcp

    Or add it to ~/.codex/config.toml, which the Codex CLI, the IDE extension and the ChatGPT desktop app share:

    ~/.codex/config.toml

    [mcp_servers.etabli]url = "https://api.etabli.io/mcp"
  • Cursor

  • VS Code (Copilot)

    Add to VS Code

    Or run code --add-mcp '{"name":"etabli","type":"http","url":"https://api.etabli.io/mcp"}'.

  • Gemini CLI

    gemini mcp add --transport http etabli https://api.etabli.io/mcp
  • Skill (70+ agents)

    npx skills add https://etabli.io

    Installs Établi's skill, order-physical-work, into the agents on your machine: when to use Établi and how to order with it.

From the command line

The etabli CLI and TypeScript SDK aren't on npm yet. Until they are, use HTTP: from a shell, curl and jq cover the whole flow.

REQUEST='Print 3,000 mailer boxes with our logo in one color, split across our warehouses in New Jersey, Texas and Nevada, within 4 weeks. Up to $1.20 a box.' curl -sX POST https://api.etabli.io/v1/quotes/preview \  -H "Content-Type: application/json" \  -d "$(jq -n --arg request "$REQUEST" '{request: $request}')" \  | jq '.legs[] | {key, title, questions: [.questions[] | {text, suggestedAnswer}]}' # Draft it for the person to claim, and print the line to show them. The saved# response holds the project key: keep the file out of git.curl -sX POST https://api.etabli.io/v1/projects \  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \  -d "$(jq -n --arg request "$REQUEST" '{request: $request}')" \  | tee .etabli-draft.json | jq -r '.project.next.message'

To set up the agents on your machine, npx skills add https://etabli.io installs Établi's skill, and each agent's own command adds the MCP server (every client).

Good to know