Documentation
GET /api/offer
The researched offer for the session's product, or the refusal that says why there is none.
| Request | GET /api/offer |
|---|---|
| Authentication | Session cookie required |
| Handler | backend/server.py |
What it does
Resolves the product the same way `/api/ad` does — claimed first, then the hold, never a direct read of the holds table. A product with no research row is not 'no viable offer'; it is `not_yet_researched`, and collapsing the two would let an unrun script report itself as a market conclusion. The fairness disclosure and the freshness line are computed at serve time rather than stamped at generation, because a property of price and product belongs where it is read: stamping it at generation left the longest-standing offers without it. A price the engine would no longer stand behind is refused with `failed_leg: "pricing"` rather than served.
Authentication
Session cookie required. Send the ff_session cookie. Without one the engine treats the caller as a new visitor with no holds and no claims, and returns a new cookie to use from then on.
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
ff_session | cookie | string | Optional | The session identifier. Omit it on the first call and store the value the response sets; every later call must send the same one or the engine treats the caller as a new visitor with no holds and no claims. |
Request
The cookie jar carries the session between calls. Angle-bracketed values are the parameter types from the table above.
curl -s -b cookies.txt -c cookies.txt \
"https://flowfinds.ai/api/offer"const res = await fetch("https://flowfinds.ai/api/offer", {
method: "GET",
credentials: "include",
});
const data = await res.json();import requests
s = requests.Session()
r = s.get("https://flowfinds.ai/api/offer")
data = r.json()Recorded example
curl -s -b cookies.txt -c cookies.txt \
"$FLOWFINDS_ORIGIN/api/offer"Responses
200 — A live offer exists.
| Field | Type | Description |
|---|---|---|
status | string | `offer`. |
offer | object | `our_price_usd`, `observed_low_usd`, `undercut_pct`, `undercut_basis`, `margin_pct`, `margin_basis`. |
freshness | string | The dated freshness line, computed at serve time. |
refresh_failed | boolean | True when the last attempt to refresh the observed listings failed — the dated observation survives, and says so. |
comparison | object | The basis the undercut is measured against. |
bundle | object | The bundle deal derived from the offer, when one applies. |
pre_shock | object | `basis` (`observed` or `unverified`), `normal_price_usd` and a `note` stating what the undercut is measured against when the pre-shortage price was never established. |
excluded_costs | array | Costs the margin figure does not include. |
watch | object | This session's watch state on the product, when the product is not servable to it. |
200 — No offer can be served.
| Field | Type | Description |
|---|---|---|
status | string | `refused`. |
failed_leg | string | `research` when the product has not been through supplier research, `pricing` when the price failed the pre-shortage check. |
reason | string | `not_yet_researched`, or the pricing refusal's own reason. |
explanation | string | The sentence the screen shows. |
excluded_costs | array | Costs the margin figure would not have included. |
Errors
| Status | Reason | When |
|---|---|---|
429 | — | The usage meter refused the call. |
Related
GET /api/dashboard— The verdict, the facts behind it, and where each figure came from.GET /api/launch— The launch facts, each with whether it is true today.POST /api/outcome— Declare what happened with a claimed product.POST /api/product— Publish a product of the founder's own into the store engine.POST /api/product-launch— Drive the launch state machine for a product.POST /api/presence— Record which screen the operator is on.
Back to the API reference index, or read the cookbook for recipes that compose this endpoint with others.