Building with a coding agent
llms.txt, a starter prompt, and integration rules.
These docs are written to be read by coding agents such as Claude Code, Cursor, or Codex, as well as by people.
Machine-readable resources
| Resource | URL | Use it for |
|---|---|---|
llms.txt | /llms.txt | An index of every guide, with links. |
llms-full.txt | /llms-full.txt | All guides in one Markdown file, for a single fetch. |
| OpenAPI 3.1 | /openapi.json | Exact request and response schemas. Generate types or a client from it. |
| Guides as Markdown | /docs/{guide}.md, for example /docs/quickstart.md | Fetching one guide without HTML. |
Both hosts serve the same documents; the prompt below reads them from the sandbox. If your agent's browser cannot open text/markdown, use llms-full.txt or the HTML pages at /docs/{guide}.
A prompt to start with
Paste this into your agent, and adjust the first paragraph to your product:
Integrate the Packflip Partner API into this project so users can buy card packs,
see their cards, and sell cards back.
Read https://sandbox.partner.packflip.xyz/llms-full.txt first, and use
https://sandbox.partner.packflip.xyz/openapi.json as the source of truth for
request and response shapes. Follow the "Off-chain lifecycle" guide in it for
the purchase flow.
Requirements:
- Call the API only from server code. Read PACKFLIP_BASE_URL, PACKFLIP_API_KEY,
and PACKFLIP_WEBHOOK_SECRET from the environment. Default the base URL to
https://sandbox.partner.packflip.xyz.
- Create one Packflip customer per user, with externalUserId set to our user ID,
and store the returned cus_ ID. On 409, look the customer up by external ID.
- Send an Idempotency-Key on every POST /api/v2/operations. Derive it from a
database record created before the call, and reuse it on retries.
- Treat money as decimal strings. Never convert to floating point.
- Add a webhook route that verifies webhook-signature against the raw body,
deduplicates on the event id, and refreshes state from the API.
- Branch on error.code, not on the HTTP status: 409 can be
insufficient_balance, out_of_stock, conflict, or idempotency_conflict.
- Offer off-chain buyback and redemption only for cards whose status is active,
sealed is false, and onchain is null. Re-fetch cards after every operation.
- Write tests against the sandbox or with recorded responses; do not call
production from tests.
Rules an integration must follow
- Keys stay on the server. An API key controls your whole balance.
- One environment per configuration. A base URL, key, and webhook secret always belong together. Sandbox is
https://sandbox.partner.packflip.xyz; production ishttps://partner.packflip.xyz. - Idempotency on every operation. Retries must reuse the key; new purchases must use a new one.
- Decimal money. Amounts are strings such as
"1.500000". - Strict requests, tolerant responses. Unknown request fields are rejected; unknown response fields must be ignored.
- Webhooks are hints. Verify, deduplicate, and re-read the resource.
- On-chain transactions come from the user's wallet. The API returns calldata; it never takes custody of user keys.
A minimal client
// packflip.ts: server-only
const baseUrl = process.env.PACKFLIP_BASE_URL ?? "https://sandbox.partner.packflip.xyz";
const apiKey = process.env.PACKFLIP_API_KEY!;
export class PackflipError extends Error {
constructor(readonly status: number, readonly code: string, message: string) {
super(message);
}
}
export async function packflip<T>(
path: string,
init: { method?: string; body?: unknown; idempotencyKey?: string } = {},
): Promise<T> {
const response = await fetch(`${baseUrl}/api/v2${path}`, {
method: init.method ?? (init.body ? "POST" : "GET"),
headers: {
Authorization: `Bearer ${apiKey}`,
...(init.body ? { "Content-Type": "application/json" } : {}),
...(init.idempotencyKey ? { "Idempotency-Key": init.idempotencyKey } : {}),
},
body: init.body ? JSON.stringify(init.body) : undefined,
cache: "no-store",
});
if (response.status === 204) return undefined as T;
const json = await response.json();
if (!response.ok) throw new PackflipError(response.status, json.error?.code, json.error?.message);
return json as T;
}
// Example: draw one pack for a customer.
export function drawPack(customerId: string, vendingMachineId: number, orderId: string) {
return packflip<{ id: string; status: string; cards: unknown[]; replayed: boolean }>("/operations", {
body: { kind: "order", customerId, vendingMachineId, quantity: 1 },
idempotencyKey: `order-${orderId}`,
});
}
For typed responses, generate types from the OpenAPI document, for example with npx openapi-typescript https://sandbox.partner.packflip.xyz/openapi.json -o packflip.d.ts. For a complete purchase flow with retries, see Off-chain lifecycle.