Skip to main content

Track B — Register the listing

This track creates rows in the marketplace database. All endpoints below require a platform_admin session. Run them in this order.

Step 1 — Create the agent

POST /agents

POST /agents
Cookie: better-auth-session=...
Content-Type: application/json

{
"name": "MyAgent",
"slug": "my-agent",
"tagline": "Short product line, 1 sentence.",
"description": "Long marketing description (used in detail page hero).",
"longDescription": "Optional multi-paragraph copy.",
"iconUrl": "https://cdn.example.com/icons/my-agent.png",
"photoUrls": [
"https://cdn.example.com/screenshots/my-agent-1.png",
"https://cdn.example.com/screenshots/my-agent-2.png"
],
"productDomain": "https://app.my-agent.com",
"docsUrl": "https://docs.my-agent.com",
"changelogUrl": "https://my-agent.com/changelog",
"status": "draft",
"featured": false,
"isNew": true,
"redirectUris": [
"https://app.my-agent.com/api/auth/oauth2/callback/marketplace",
"http://localhost:3001/api/auth/oauth2/callback/marketplace"
],
"tagIds": ["<tagId for 'voice'>", "<tagId for 'support'>"]
}

Response wraps the new agent in the ApiResponse envelope:

{
"status": "success",
"data": {
"id": "ag_01HX...",
"slug": "my-agent",
"oauthClientId": "oac_01HX...",
"redirectUris": [...]
}
}

Save both:

  • data.id — your AGENT_ID for all later API calls and Track A.
  • data.oauthClientId — set this as OAUTH_CLIENT_ID in Track A's .env.

The OAuth client is created as public (tokenEndpointAuthMethod: 'none'), so there's no secret. PKCE-S256 is enforced.

Redirect URIs

You can update redirectUris later via POST /agents/:id (full update). The list is mirrored into the OAuth client row. Always include:

  • The production callback (https://app.my-agent.com/api/auth/oauth2/callback/marketplace).
  • Every local dev origin you'll use (http://localhost:3001/..., http://localhost:3000/...).
  • Any preview environments (Vercel preview URLs, etc.).

Step 2 — Add capabilities

POST /agents/:agentId/capabilities — repeat for each bullet.

{
"icon": "shield-check",
"title": "Secure by default",
"description": "OAuth-based identity, encrypted at rest.",
"sortOrder": 1
}

icon is a slug looked up by the frontend's LucideDynamicIcon component. Use any Lucide icon name lowercased and kebab-cased.

Step 3 — Add steps

POST /agents/:agentId/steps

{
"title": "Connect your domain",
"description": "Drop our widget script into your site and we'll crawl your docs.",
"sortOrder": 1
}

Same CRUD as capabilities. Order matters: this is the "how it works" strip on the detail page.

Step 4 — Add changelog entries

POST /agents/:agentId/changelog

{
"version": "0.1.0",
"summary": "Initial release.",
"publishedAt": "2026-05-14T00:00:00.000Z"
}

Optional but recommended — the detail page renders a changelog tab.

Step 5 — Define pricing tiers

POST /agents/:agentId/pricing — one call per tier.

Free tier:

{
"tierName": "Free",
"isFree": true,
"price": "0",
"currency": "usd",
"billingInterval": "month",
"featuresIncluded": ["1 site", "100 sessions/mo"],
"recommended": false,
"trialDays": 0,
"sortOrder": 1
}

Paid tier (auto-creates Stripe Product + Price):

{
"tierName": "Growth",
"isFree": false,
"price": "49.00",
"currency": "usd",
"billingInterval": "month",
"featuresIncluded": ["3 sites", "500 sessions/mo", "Voice replies"],
"recommended": true,
"trialDays": 7,
"sortOrder": 2
}

If STRIPE_SECRET_KEY is configured on the backend, the response will include stripeProductId and stripePriceId. Save them — they're persisted on agent_pricing_tiers. The marketplace handles the rest of the Stripe lifecycle.

The marketplace does not create Stripe Customers up front. They're created on the first paid checkout.

Step 6 — Define metric definitions

If your agent has tier-gated features or per-cycle limits, declare each one.

POST /agents/:agentId/metric-definitions

{
"slug": "sessions",
"displayName": "Sessions",
"unit": "session",
"kind": "rolling",
"sortOrder": 1
}
{
"slug": "sites",
"displayName": "Knowledge bases",
"unit": "kb",
"kind": "fixed",
"sortOrder": 2
}
{
"slug": "voice",
"displayName": "Voice replies",
"unit": "boolean",
"kind": "fixed",
"sortOrder": 3
}
FieldRule
slugImmutable. Used in every agent-side consume/release call. Choose carefully.
kindImmutable. fixed = total allocation, never resets. rolling = resets each billing period.
unitFree text, displayed in the dashboard ("250 calls", "10 GB", "5 seats").

Step 7 — Set tier quotas per metric

For each (tier, metric) pair:

PUT /agents/:agentId/pricing/:tierId/quotas/:metricId

{ "quotaLimit": 500 }
quotaLimit valueMeaning
nullUnlimited
0Explicit deny (feature gated off)
Positive integerCap

No row at all = treated as 0 (deny). Always set a row for every (tier, metric) pair, even if just to null for "unlimited on Enterprise."

Step 8 — Tag the agent

If you didn't pass tagIds at create time, you can attach tags via the agent update endpoint. Tags drive the category filter and the category theme in the UI.

GET /tags lists existing ones. POST /tags (admin-only) creates a new one. Slugs auto-generate from name.

Step 9 — Verify

# 1. Catalog visibility
curl https://api.fleapo.ai/agents/by-slug/my-agent | jq

# 2. Pricing surface
curl https://api.fleapo.ai/agents/<agentId>/pricing | jq

# 3. Metric definitions
curl https://api.fleapo.ai/agents/<agentId>/metric-definitions | jq

# 4. Quotas for free tier
curl https://api.fleapo.ai/agents/<agentId>/pricing/<freeTierId>/quotas | jq

All four should return non-empty data arrays inside an ApiResponse envelope.

What you've created

TableRows
agents1
oauth_clients (better-auth)1, linked by oauthClientId
agent_tagsN (one per tag)
agent_capabilitiesN
agent_stepsN
agent_changelogN
agent_pricing_tiersN (Stripe Product + Price per paid tier)
agent_metric_definitionsN
tier_metric_quotasN × M (every tier × metric you defined)

Next steps

  • Hand oauthClientId, id, slug to Track A.
  • Move to Track C to confirm UI rendering and (optionally) add a category theme.
  • When everything passes, run the end-to-end checklist.