Readiness system

The readiness system gives agents a reliable, structured answer to the question: “Can this listing sell right now, and if not, what needs to happen?”

Every listing response includes a readiness object. Agents read it, act on it, and loop until sellable is true. No guesswork, no polling, no out-of-band state.


The readiness object

1{
2 "object": "listing",
3 "id": "lst_...",
4 "readiness": {
5 "sellable": false,
6 "actions": [
7 {
8 "code": "connect_stripe",
9 "kind": "human",
10 "message": "Connect a Stripe account to start accepting payments.",
11 "resolve": {
12 "method": "GET",
13 "endpoint": null,
14 "url": "https://console.listbee.so/settings/payments",
15 "params": null
16 }
17 }
18 ],
19 "next": "connect_stripe"
20 }
21}

Three fields:

FieldTypeDescription
sellablebooleantrue if buyers can purchase right now. false if anything is blocking.
actionsarrayOrdered list of actions that must be taken to reach sellable: true. Empty when sellable is true.
nextstring | nullCode of the highest-priority action. Prefers kind: "api" over kind: "human". null when sellable is true.

Decision tree

Use this logic in any agent that needs to drive a listing to sellable:

is readiness.sellable true?
→ yes: listing is live, nothing to do
→ no: read readiness.next to get the action code
find that action in readiness.actions
is action.kind "api"?
→ yes: call action.resolve.method action.resolve.endpoint
with action.resolve.params
then re-fetch the listing and repeat
→ no (kind "human"): surface action.resolve.url to the user
wait for user to complete the step
then re-fetch and repeat

Actions explained

Each action in the actions array has four fields:

FieldTypeDescription
codestringMachine-readable identifier. Use this to branch logic.
kind"api" | "human""api" = agent can call directly. "human" = user must visit a URL.
messagestringHuman-readable description of what’s blocking.
resolveobjectHow to fix it: method, endpoint (for api), url (for human), params.

kind: api

The agent can resolve this action autonomously. Call resolve.method on resolve.endpoint with resolve.params as the request body.

$# If action.kind is "api", call the resolve endpoint:
$curl -X POST https://api.listbee.so/v1/account/stripe-key \
> -H "Authorization: Bearer lb_..." \
> -H "Content-Type: application/json" \
> -d '{"secret_key": "sk_live_..."}'

kind: human

The agent cannot resolve this action. A human must visit resolve.url in a browser to complete the step — for example, completing Stripe Connect onboarding or updating billing details.

Action required: Connect a Stripe account to start accepting payments.
Visit: https://console.listbee.so/settings/payments

The next pointer

next is the code of the highest-priority action. When multiple actions are blocking, next points to the one the agent should act on first.

The selection rule: if any kind: "api" actions are present, next points to one of those. If only kind: "human" actions remain, next points to the human action.

This means an agent can always just follow next without reasoning about the full actions array.

To act on the next pointer: fetch the listing, find the action in actions where code == readiness.next, then either call the resolve.endpoint (for kind: "api") or surface resolve.url to the user (for kind: "human"). Re-fetch the listing after each action and repeat until sellable is true.


Action code reference

CodeKindWhen triggeredHow to resolve
connect_stripehumanNo Stripe connected. No key available.Visit console.listbee.so/settings/payments to start Stripe Connect.
set_stripe_keyapiNo Stripe connected. Agent has a key to submit.POST /v1/account/stripe-key with { "secret_key": "sk_live_..." }
enable_chargeshumanStripe connected but charges disabled.Visit the URL in resolve.url to complete Stripe’s requirements.
update_billinghumanSubscription billing past due.Visit console.listbee.so/settings/billing to update payment method.
configure_webhookapiExternal fulfillment listing with no webhook.POST /v1/webhooks with { "name": "...", "url": "https://...", "events": ["order.paid"] }
publish_listingapiListing is in draft state.POST /v1/listings/{id}/publish
resume_listingapiListing is paused.POST /v1/listings/{id}/resume
webhook_disabledapiWebhook auto-disabled after failures.PUT /v1/webhooks/{id} with { "enabled": true }

set_stripe_key and connect_stripe are mutually exclusive. set_stripe_key appears when the agent already has a Stripe key to submit. connect_stripe appears when the user must go through the full Stripe Connect onboarding flow in a browser.

configure_webhook only appears on listings with fulfillment: "external". External listings need a webhook to receive order.paid events — without one, the listing cannot sell because no one would know about the order. Register a webhook endpoint to resolve this action.


Full JSON examples

Sellable listing

A listing with Stripe connected, charges enabled, and content attached.

1{
2 "object": "listing",
3 "id": "lst_abc123",
4 "slug": "python-async-patterns",
5 "url": "https://buy.listbee.so/python-async-patterns",
6 "fulfillment": "managed",
7 "deliverable_type": "file",
8 "readiness": {
9 "sellable": true,
10 "actions": [],
11 "next": null
12 }
13}

Listing with no Stripe (two actions)

set_stripe_key is kind: "api" so next points to it.

1{
2 "object": "listing",
3 "id": "lst_abc123",
4 "slug": "python-async-patterns",
5 "readiness": {
6 "sellable": false,
7 "actions": [
8 {
9 "code": "set_stripe_key",
10 "kind": "api",
11 "message": "Submit your Stripe secret key to enable payments.",
12 "resolve": {
13 "method": "POST",
14 "endpoint": "/v1/account/stripe-key",
15 "url": null,
16 "params": { "secret_key": "<your_stripe_secret_key>" }
17 }
18 },
19 {
20 "code": "connect_stripe",
21 "kind": "human",
22 "message": "Connect a Stripe account to start accepting payments.",
23 "resolve": {
24 "method": "GET",
25 "endpoint": null,
26 "url": "https://console.listbee.so/settings/payments",
27 "params": null
28 }
29 }
30 ],
31 "next": "set_stripe_key"
32 }
33}

External listing with no webhook

1{
2 "object": "listing",
3 "id": "lst_abc123",
4 "slug": "custom-portrait",
5 "fulfillment": "external",
6 "readiness": {
7 "sellable": false,
8 "actions": [
9 {
10 "code": "configure_webhook",
11 "kind": "api",
12 "message": "Register a webhook endpoint to receive order.paid events for external fulfillment.",
13 "resolve": {
14 "method": "POST",
15 "endpoint": "/v1/webhooks",
16 "url": null,
17 "params": { "url": "<your_webhook_url>", "events": ["order.paid"] }
18 }
19 }
20 ],
21 "next": "configure_webhook"
22 }
23}

Listing with charges disabled

Stripe is connected, but Stripe has disabled charges on the account (e.g. pending verification).

1{
2 "object": "listing",
3 "id": "lst_abc123",
4 "slug": "python-async-patterns",
5 "readiness": {
6 "sellable": false,
7 "actions": [
8 {
9 "code": "enable_charges",
10 "kind": "human",
11 "message": "Stripe has disabled charges on your account. Complete Stripe's requirements to re-enable.",
12 "resolve": {
13 "method": "GET",
14 "endpoint": null,
15 "url": "https://dashboard.stripe.com/account/status",
16 "params": null
17 }
18 }
19 ],
20 "next": "enable_charges"
21 }
22}

How other APIs handle this

They don’t. Stripe Payment Links, Gumroad, and Lemon Squeezy all return a listing or product object with no machine-readable signal about whether it can sell. An agent calling those APIs has no way to know if payment is misconfigured, if charges are disabled, or what the next required action is — it either errors at checkout time or requires a human to inspect the console.

ListBee’s readiness system is purpose-built for agent-operated commerce. An agent can drive a listing from zero to sellable: true in a single loop without any human input — unless the blocking action is kind: "human", in which case the agent surfaces the exact URL.


Next steps

  • Agent onboarding flow — see the readiness system in action during a full onboarding dialogue.
  • Payments — connect Stripe to resolve the most common readiness actions.

Copy for AI assistants

Cursor / Claude Code
$# ListBee — readiness system
$#
$# Every listing response includes readiness: { sellable, actions, next }
$#
$# sellable: true → listing is live, buyers can purchase
$# sellable: false → follow the actions array to fix it
$#
$# Algorithm:
$# 1. Check readiness.sellable
$# 2. If false, get action where action.code == readiness.next
$# 3. If action.kind == "api":
$# call action.resolve.method on action.resolve.endpoint
$# with action.resolve.params as body
$# re-fetch listing, repeat
$# 4. If action.kind == "human":
$# surface action.resolve.url to the user
$# wait for completion, re-fetch, repeat
$#
$# Action codes:
$# connect_stripe human visit console.listbee.so/settings/payments
$# set_stripe_key api POST /v1/account/stripe-key { "secret_key": "sk_live_..." }
$# enable_charges human visit action.resolve.url (Stripe dashboard)
$# update_billing human visit console.listbee.so/settings/billing
$# configure_webhook api POST /v1/webhooks { "name": "...", "url": "...", "events": ["order.paid"] }
$# publish_listing api POST /v1/listings/{id}/publish
$# resume_listing api POST /v1/listings/{id}/resume
$# webhook_disabled api PUT /v1/webhooks/{id} { "enabled": true }
$#
$# configure_webhook appears only on external fulfillment listings without a webhook.
$# next pointer prefers kind:"api" over kind:"human"
$# next is null when sellable is true
$#
$# Auth: Authorization: Bearer lb_...
$# Errors: RFC 9457 { type, title, status, detail, code, param }