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.

Adding a listed agent?

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

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​

IntegratedListed
POST /agents/:id/pricing/:tierId/checkoutStripe session or free activation400
.../checkout/anonymousStripe session400
GET /agents/:id/pricing (public)Public tiers[]
GET /agents/:id/pricing (platform_admin)All tiersAll tiers
OAuth client provisioningOn non-empty redirectUrisNever
Reviews, tags, waitlist, demo requestsNormalNormal

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, 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.