DocsStart

Quickstart and API

Run it, call it, approve an order.

4 min readEdit on GitHub

#Requirements

Node.js 22.18 or newer. That release runs TypeScript files directly, so there is no build step. Check with node --version.

#Run

bash
npm install          # TypeScript and Node types, for `npm run typecheck` only
npm run demo         # full story, you approve in the terminal
npm run demo -- --yes
npm start            # gateway on http://127.0.0.1:8080 plus the mock network, until Ctrl+C
npm test             # 126 tests
npm run lint
npm run check        # typecheck + lint + tests (what CI runs)
npm run serve        # production entry (src/main.ts), configured by environment variables
docker compose up --build

Environment variables: see .env.example. The most useful: HAAT_DB (a SQLite file; without it state is in memory and lost on restart), HAAT_PORT, HAAT_PUBLIC_URL, and HAAT_LOG (debug, info, warn, error, silent).

#Talking to the gateway

Every API and conversation request needs a Poppy-Session header: a JWT the Personal Agent signs for that exact request (Poppy draft 0.3, section 5.2). Plain curl can't make one, so use the reference agent in agent/personal-agent.ts:

ts
import { PersonalAgent } from './agent/personal-agent.ts';

const agent = await new PersonalAgent({ name: 'My agent' }).start(); // serves agent.json + jwks.json
const company = await agent.discover('http://127.0.0.1:8080');
const sid = agent.newSession();

const search = await agent.call(company, sid, 'GET', '/api/v1/search?q=' + encodeURIComponent('2 kilo atta aur 1 litre doodh'));
const b = search.body.suggested_basket;

const order = await agent.callWithApproval(company, sid, 'POST', '/api/v1/orders', {
  bpp_id: b.bpp_id,
  provider_id: b.provider_id,
  items: b.items.map((i) => ({ id: i.id, count: i.count })),
  delivery: { name: 'Asha', phone: '9876543210', address: { city: 'Varanasi', state: 'Uttar Pradesh', area_code: '221005' } },
}, async (op) => {
  console.log(op.summary);          // show this to the person
  return true;                      // only if they said yes
});

#Signing in

ts
import { personSignsIn } from './agent/personal-agent.ts';

// In real use the person opens this URL in their browser. personSignsIn plays that part locally.
await agent.signIn(company, ['orders:read', 'orders:write', 'addresses'], (url) =>
  personSignsIn(url, { phone: '9876543210', otp: '123456' }), // the development code; never in production
);
await agent.call(company, sid, 'GET', '/api/v1/orders', { signedIn: true });
await agent.signOut(company);

Signed in, orders belong to the account (visible from every Session and agent of that person), and delivery_address_id can replace delivery when ordering.

#Endpoints

Method and pathPurpose
GET /.well-known/poppy.jsonDiscovery document (draft 0.3)
GET /.well-known/oauth-protected-resource404: no OAuth issuer, signed-out Sessions only
GET /openapi.jsonOpenAPI 3.1 description of the API
GET /api/v1/search?q=…&city=std:0542Parse the list, search the network, suggest a single-seller basket
POST /api/v1/ordersPlace an order. First answer: 403 approval_required with an operation
GET /api/v1/ordersThis User's orders
GET /api/v1/orders/{id}Live status from the seller
POST /api/v1/orders/{id}/cancelCancel, also behind approval
POST /poppy/conversationsStart a conversation with the Company Agent
POST /poppy/conversations/{id}/messagesSend a message (?wait=10 holds for the reply)
GET /poppy/conversations/{id}/eventsRead events (cursor, wait)
POST /poppy/conversations/{id}/handoffAsk for a person (none available yet; says so)
POST /poppy/conversations/{id}/closeClose
POST /beckn/on_*Signed callbacks from sellers
GET /healthLiveness and loaded packs
GET /.well-known/oauth-protected-resource, GET /.well-known/oauth-authorization-serverSign-in discovery
GET, POST /oauth/authorize; POST /oauth/token; POST /oauth/revokeDirect Sign-In, tokens, sign-out
GET, POST /api/v1/account/addresses; DELETE /api/v1/account/addresses/{id}Saved addresses (scope addresses)
GET /poppy/excluded-tasks.json, POST /poppy/feedbackTask coverage (section 9)
GET /ondc-site-verification.html, POST {callback}/on_subscribeONDC onboarding (ONDC mode)

#Errors you will see

StatuserrorMeaning
400invalid_sessionSession header missing or failed verification; error_description says which check
401invalid_token (in WWW-Authenticate)The access token is unknown, expired or revoked: refresh, or continue signed out
401no error codeThe request needs the account: sign in with the scope in the challenge
403insufficient_scopeThe token lacks a scope; sign in again for it
403approval_requiredShow operation.summary, then repeat the same request with Poppy-Approval
403extension_requiredThe agent's client metadata does not list operations version 2
404unknown_seller, order_not_foundSeller not in the registry, or the order belongs to another User
409seller_<code>The seller refused (out of stock 40002, too late to cancel 50001)
409message_id_conflict, conversation_closedConversation rules (Poppy 8.3, 8.12)
400 / 410invalid_cursor / cursor_expiredA cursor from elsewhere, or for events no longer kept (8.13)
429rate_limitedWait for Retry-After
503operation_in_progressThe outcome is not known yet; send the same approval again after Retry-After