Skip to content
v0.3

API: your first call

Everything below was run against a live Forge instance before it was published. The ids are real; substitute your own host and they work.

We use https://your-instance.example for the host. If you are following along on a demo instance, use its hostname.

1. Read something — no credential at all

Section titled “1. Read something — no credential at all”

The public read face answers anyone. Start by resolving the store from the hostname:

Terminal window
curl "https://your-instance.example/v1/read/store.by_host?host=your-instance.example"
{ "store_id": "sto_01ABCDEFGHJKMNPQRSTVWXYZ01" }

That id is the store parameter for every other public read — yours, from your instance. Every id on this page has the right shape and is not a real one; store.by_host above is how you get the one that answers for you.

Now list some products:

Terminal window
curl "https://your-instance.example/v1/read/products?store=sto_…&limit=2&projection=feed"

2. Change something — still no credential

Section titled “2. Change something — still no credential”

Building a cart is the shopper face: anonymous, scoped to a store, and the cart id is the capability. Exactly what a browser does.

Terminal window
curl -X POST "https://your-instance.example/v1/cart/commands/cart.create" \
-H "content-type: application/json" \
-H "x-forge-store: sto_…" \
-d '{}'
{ "cart_id": "cart_01ABCDEFGHJKMNPQRSTVWXYZ01" }
Terminal window
curl -X POST "https://your-instance.example/v1/cart/commands/cart.add_line" \
-H "content-type: application/json" \
-H "x-forge-store: sto_…" \
-d '{"cart_id": "cart_…", "sku_id": "sku_…", "qty": 1}'
{ "line_id": "cl_01ABCDEFGHJKMNPQRSTVWXYZ01", "qty": 1 }
Terminal window
curl "https://your-instance.example/v1/read/cart?store=sto_…&cart_id=cart_…"
{
"cart_id": "cart_01ABCDEFGHJKMNPQRSTVWXYZ01",
"currency": "BRL",
"subtotal": 20000,
"total": 20000,
"lines": [
{ "line_id": "cl_…", "sku_id": "sku_…", "qty": 1, "unit_amount": 20000, "line_total": 20000, "available": 95 }
]
}

Everything so far was anonymous. To read what an operator reads, you need an API key. Issue one in the admin under Team → API keys, with only the scopes you need, and export it:

Terminal window
export FORGE_TOKEN='fopk_…'
Terminal window
curl -i -H "Authorization: Bearer $FORGE_TOKEN" \
"https://your-instance.example/v1/read/internal/whoami"
HTTP/2 200
ratelimit-limit: 6000
ratelimit-remaining: 5999
ratelimit-reset: 28
{
"actor_id": "act_…",
"tenant_id": "…",
"scopes": ["catalog.admin.read", "logistics.read", "order.read"]
}

Two things to notice:

The RateLimit-* headers came back on a success, not on a 429. The state of your budget is part of every authenticated response, so you can design a backoff before you need one. The subject of the limit is the credential, per tenant — two keys of the same customer have independent budgets.

whoami tells you what you hold. It is the fastest way to check that a key carries what you think it carries.

Ask for something the key does not carry:

Terminal window
curl -i -H "Authorization: Bearer $FORGE_TOKEN" \
"https://your-instance.example/v1/read/internal/api_keys"
{ "error": { "kind": "forbidden", "message": "read requires the admin.users.write scope" } }

403, and the message names the scope. Not a 404, not an empty list, not a silence you have to diagnose. That is the kernel’s own check — the same one every other surface reaches, so a scope withheld is withheld everywhere at once.

Face Credential For
GET /v1/read/<name> none Anything a shopper’s browser may see
POST /v1/cart/commands/<name> none The cart journey; the cart id is the capability. There are sibling anonymous faces — a command’s reference page names its exact route
POST /v1/commands/<name> tenant Operator and integration writes
GET /v1/read/internal/<name> tenant Operator reads, gated by scope

The platform face (/v1/control/…) exists too, for whoever runs the fleet.