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— 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) |
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.