Documentation
Guides
Task-shaped walkthroughs for the things people actually build.
Each guide below is a task, start to finish, built only from capabilities that exist today. Where a step has a failure mode worth knowing about, the guide says so, because a walkthrough that only describes the happy path is a walkthrough you will abandon the first time something refuses.
- Claim a product and open an account
- Build, verify and publish a storefront
- Change the store through guided intents
- Produce an ad artifact and fund it
- Research and approve an organic campaign
- Run the machine after the sale
- Report an outcome and read the track record
Claim a product and open an account
Discovery is exclusive: a product held by one session is not servable to another, and the hold that makes that true is minted in exactly one place in the engine. Everything downstream — the offer, the store, revenue, tracking — resolves through that same hold or the claim it became, which is why this sequence has to come first.
# 1. Be shown the field. The first call mints the session.
curl -s -c jar -b jar "$FLOWFINDS_ORIGIN/api/feed"
# 2. Move the hold onto the card you want. feed[0] is the recommendation,
# not a restriction — every card returned is claimable.
curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' \
-d '{"product_id":"<id>"}' "$FLOWFINDS_ORIGIN/api/select"
# 3. Claim it. The email is what the claim is credited to.
curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' \
-d '{"email":"[email protected]","product_id":"<id>"}' \
"$FLOWFINDS_ORIGIN/api/pass"
# 4. Create the account the claim now attaches to.
curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' \
-d '{"email":"[email protected]"}' "$FLOWFINDS_ORIGIN/api/account"Three things about this that are not obvious from the endpoint names. First, the hold lasts thirty minutes and the response tells you so in held_minutes rather than leaving you to assume it. Second, the email arrives at the end: the claim is created before the account exists, and POST /api/account adopts it, returning the adopted claims in adopted_claims. Third, a refusal here is a 200 with a named reason — all_held, catalog_exhausted, passed_out and claim_limit_reached are four distinct situations with four distinct exits, and only the last of them is fixed by an action you can take.
Read GET /api/feed and POST /api/pass for the full contracts.
Build, verify and publish a storefront
The store engine generates a storefront for the claimed product. The state machine is worth understanding before you write the polling loop, because ‘ready’ is not asserted from the build’s own manifest: the store must exist on disk and its browsable path must resolve, or the status is whatever is actually true instead. A manifest is the generator grading itself.
# Start the build.
curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' -d '{}' \
"$FLOWFINDS_ORIGIN/api/store"
# Poll until status leaves the running stages.
until curl -s -c jar -b jar "$FLOWFINDS_ORIGIN/api/store" \
| grep -qE '"status": *"(ready|failed|not_started|not_claimed)"'; do
sleep 5
done
# Verify and publish. 'regenerated' says whether publishing had to rewrite it.
curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' -d '{}' \
"$FLOWFINDS_ORIGIN/api/store/publish"The four terminal states are ready, failed, not_started and not_claimed. The distinction between the last two matters: not_started means no build was ever attempted, while a finished job with no manifest is reported as failed with a failed_stage and the generator’s own diagnostic. Reporting the second as the first would lose the attempt and make the control look dead.
Publishing verifies before it confirms. When verification forces a rewrite, regenerated comes back true — a publish that had to rewrite the store did more than confirm it, and you are told which of the two happened.
Change the store through guided intents
The intent loop exists so that a change to a live storefront always carries a reason. The engine will not propose an intent it cannot justify with a dated row, and it will not apply a proposal the caller did not see.
# 1. Propose. Nothing is applied; the engine answers with what it would do
# and the dated row that justifies it.
PROPOSAL=$(curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' \
-d '{"surface":"<surface>","requested":"Lead with the guarantee"}' \
"$FLOWFINDS_ORIGIN/api/intent")
# 2. Apply, echoing the proposal back verbatim. A proposal the engine would no
# longer make is refused as stale_proposal rather than applied silently.
curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' \
-d "{\"surface\":\"<surface>\",\"requested\":\"Lead with the guarantee\",\"proposed\":$PROPOSAL}" \
"$FLOWFINDS_ORIGIN/api/intent/apply"
# 3. If it was wrong, undo it. The response names what it reverted from.
curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' -d '{}' \
"$FLOWFINDS_ORIGIN/api/intent/revert"Two refusals define the loop. no_proposal_echoed fires when the apply request carries no proposal: an apply that trusted the request alone could commit a change the founder never reviewed. stale_proposal fires when the echoed proposal no longer matches what the engine would propose now, which is what makes the round trip safe rather than decorative. A change that cannot be undone requires confirm_irreversible, and the applied response carries reversible so your interface can offer the undo honestly.
Overriding the section the store leads with is a separate endpoint, POST /api/intent/order, and it returns both sides: your override stamped founder_override, and engine_dissent carrying what the engine would have led with and why. Neither fact is discarded to make the screen simpler.
For free-text edits rather than structured intents, use POST /api/ai/edit. Its changed array lists the fields actually modified, and partial_edit is a real outcome you must handle: changes remain in the draft and should be reviewed or undone before retrying.
Produce an ad artifact and fund it
GET /api/ad returns the ad specification for the claimed product together with the click-through and cost-per-click bar it must clear. The pass criteria are part of the artifact, not a separate judgement made afterwards, which is the point: an artifact that ships without a stated bar cannot be said to have failed.
Copy can be overridden with POST /api/ad/edit and restored with {"reset": true}; the generated version is never lost. Campaigns live in a single book read and written whole through /api/ads-manager, and an account that has never saved one receives the empty book rather than a 404, so your client renders one way.
Funding runs through the advertising balance. GET /api/adbalance returns the balance in minor units, the ledger behind it and any top-ups still pending confirmation. POST /api/adbalance opens a payment session and returns charged_yet: false, because opening a session is not a payment. If payments are not configured on the machine the response says so rather than rendering a control that cannot work.
Research and approve an organic campaign
The organic lane is deliberately three separate things: research, approval, and a log of what was executed. It posts nothing to any platform, because it holds no platform account, and the design keeps that limitation visible instead of hiding it behind a button.
POST /api/organic/researchwith a query and the platforms to research. An empty query is refused asno_question; an unrecognised platform asinvalid_platform.POST /api/organic/approvewith the platform, the query and the scopes the approval covers. This writes an approval row and nothing else.GET /api/organic/logto read every approval withapproved_at,executed_atandoutcome. An approval nothing was ever run against shows null for the last two, which is the honest state and the reason the log is worth reading.
Run the machine after the sale
Four endpoints cover what happens once an order exists, and each one has a stated position on what it does not know.
GET /api/tracking— this founder’s orders, scoped through the same claims lookup every other founder-scoped resolver uses. There is no demo order in the data: not flagged, not greyed, not present, because a flag can be dropped by a render or a screenshot and a row that was never created cannot be mistaken for real activity. With none, it answersno_orders_yetat 200.GET /api/revenue— the confirmed payment ledger, per store and per currency, with no exchange rate applied. Before an account exists it says there is nowhere to credit revenue to, rather than showing zero.GET /api/helpdesk— the support desk. Reading it polls the inbox, because opening Support is exactly when the operator’s real question is whether anything has just come in.POST /api/supportasks the record-bound agent a question; it answers from the account’s records, not from general knowledge.GET /api/satisfaction— the satisfaction sample and its analysis.
Report an outcome and read the track record
The published hit rate is computed from founder-reported outcomes and states its own denominator, because a rate whose basis is unstated is a boast. Reporting an outcome is how you enter that denominator.
# Report what actually happened. This is the denominator of the
# public track record, and the response stamps it provenance: "declared".
curl -s -c jar -b jar -X POST -H 'Content-Type: application/json' \
-d '{"sold":true,"spend_usd":118.40,"revenue_usd":392.00}' \
"$FLOWFINDS_ORIGIN/api/outcome"
# The published rate, computed from those reports, with its basis stated.
curl -s "$FLOWFINDS_ORIGIN/api/track-record"The response stamps provenance: "declared". Nothing in the system pretends a founder’s report is an independent observation, and the commerce agent restates the same caveat whenever it reads these rows through its outcomes tool.
Where to go next
- Cookbook — shorter recipes that compose these endpoints.
- API reference — every parameter, field and status code.
- Agents and tools — what the commerce agent can read on a founder’s behalf.
Next: Cookbook · API reference · Agents and tools