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.
Do Step 1 with "type": "listed" and an externalUrl, then Steps 2, 3 and 8 (capabilities, steps, tags) if you want them. Skip Steps 5, 6 and 7 entirely — a listed agent bills on its own site, so pricing tiers, metric definitions and quotas do nothing for it, and redirectUris would only mint an OAuth client it never uses. See Listed agents at the end of this page.
Step 1 — Create the agent
POST /agents
POST /agents
Cookie: better-auth-session=...
Content-Type: application/json
{
"type": "integrated",
"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— yourAGENT_IDfor all later API calls and Track A.data.oauthClientId— set this asOAUTH_CLIENT_IDin 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
}
| Field | Rule |
|---|---|
slug | Immutable. Used in every agent-side consume/release call. Choose carefully. |
kind | Immutable. fixed = total allocation, never resets. rolling = resets each billing period. |
unit | Free 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 value | Meaning |
|---|---|
null | Unlimited |
0 | Explicit deny (feature gated off) |
| Positive integer | Cap |
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
| Table | Rows |
|---|---|
agents | 1 |
oauth_clients (better-auth) | 1, linked by oauthClientId |
agent_tags | N (one per tag) |
agent_capabilities | N |
agent_steps | N |
agent_changelog | N |
agent_pricing_tiers | N (Stripe Product + Price per paid tier) |
agent_metric_definitions | N |
tier_metric_quotas | N × M (every tier × metric you defined) |
Listed agents
A listed agent is a catalog entry for a product that runs its own signup and billing. It is one request:
POST /agents
Cookie: better-auth-session=...
Content-Type: application/json
{
"type": "listed",
"name": "Northwind Notes",
"tagline": "Notes that write themselves",
"description": "Short pitch shown on cards.",
"longDescription": "Optional multi-paragraph copy.",
"externalUrl": "https://northwind.example.com",
"pricingUrl": "https://northwind.example.com/pricing",
"iconUrl": "https://cdn.example.com/icons/northwind.png",
"photoUrls": ["https://cdn.example.com/screenshots/northwind-1.png"],
"status": "active",
"tagIds": ["<tagId for 'productivity'>"]
}
externalUrl is required — a listed agent whose CTA points nowhere is not a listing. Omitting it returns:
{
"statusCode": 422,
"error": {
"code": "UNPROCESSABLE_ENTITY",
"message": "externalUrl is required when type is \"listed\""
}
}
What behaves differently
| Integrated | Listed | |
|---|---|---|
POST /agents/:id/pricing/:tierId/checkout | Stripe session or free activation | 400 |
.../checkout/anonymous | Stripe session | 400 |
GET /agents/:id/pricing (public) | Public tiers | [] |
GET /agents/:id/pricing (platform_admin) | All tiers | All tiers |
| OAuth client provisioning | On non-empty redirectUris | Never |
| Reviews, tags, waitlist, demo requests | Normal | Normal |
Videos still upload through POST /agents/:id/videos, which needs an existing agent — so create the agent first, then add video. The admin UI does this for you by landing on the edit page after create.
Converting a listed agent to integrated
One-way, and in place — the row keeps its slug, reviews and ranking:
POST /agents/:id
{ "type": "integrated", "redirectUris": ["https://northwind.example.com/auth/callback"] }
It is refused with a 422 unless the agent has both at least one redirect URI and at least one pricing tier (add tiers first via Step 5 — tier creation is allowed on a listed agent precisely so this gate can be satisfied). integrated → listed is always refused; set status to inactive instead.
Next steps
- Hand
oauthClientId,id,slugto 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.