API
Everything the site shows, as JSON. Reading needs no key and no sign-up. Adding a startup needs one, and files a submission for a person to read — nothing here publishes, edits or deletes.
Quickstart
The base is https://ponicorn.com. Everything under /api/v1/ is stable; other paths under /api/ are internal to the site and may change without notice. The machine description is at /api/openapi.json, and this page is rendered from it.
One request, no credential
curl "https://ponicorn.com/api/v1/startups?revenue=10k_50k&team=solo&sort=revenue"Endpoints
Four reads and one write. An unknown filter value is ignored rather than refused, so a typo returns everything rather than an error — call /api/v1/taxonomy for the slugs that exist.
GET /api/v1/startups
Search published startups
The same filters /startups uses, so a filtered URL from the site works here by changing only the path. An unknown filter value is ignored rather than refused; call /api/v1/taxonomy for the slugs that exist.
| Name | Type | Meaning |
|---|---|---|
| q | string | Name or tagline fragment; at most 80 characters. |
| category | string | Category slug. See /api/v1/taxonomy. |
| model | string | Business-model slug. |
| stack | string | Technology slug. |
| revenue | string | Current monthly-equivalent USD revenue band.under_1k · 1k_10k · 10k_50k · 50k_250k · 250k_plus |
| team | string | Team-size band.solo · small · medium · large |
| funding | string | How the business was funded.bootstrapped · angel · venture · other |
| sort | string | Ordering. Defaults to newest.newest · likes · revenue · name |
| page | integer | 1-based page number.Defaults to 1. |
| limit | integer | Rows per page.Defaults to 25. |
200 — One page of startups.
{
"data": [
{
"name": "Example",
"slug": "example",
"url": "https://ponicorn.com/startups/example",
"tagline": "What the business does, in one line.",
"website_url": "https://example.com",
"logo_url": null,
"founded_year": 2024,
"country_code": "PT",
"team_size": 1,
"funding_type": "bootstrapped",
"status": "active",
"primary_category": "developer-tools",
"founder": {
"name": "Ada Lovelace",
"slug": "ada-lovelace"
},
"founder_verified": true,
"editor_verified": false,
"likes": 34,
"headline_metric": {
"metric": "mrr",
"value": 12000,
"value_text": null,
"currency": "USD",
"measured_at": "2026-08-01",
"period_type": "monthly",
"evidence_status": "reported",
"verified_via": null,
"connected_via": "stripe",
"confidence": "high",
"label": "verified",
"is_current": true,
"verified_at": "2026-08-03",
"disputed": false,
"note": null,
"source": {
"url": "https://example.com/six-months-in",
"title": "Six months in",
"publisher": "Example Blog",
"source_type": "founder_post",
"author": "Ada Lovelace",
"published_at": "2026-08-01"
}
}
}
],
"page": {
"number": 1,
"size": 25,
"total": 63,
"pages": 3
},
"links": {
"self": "https://ponicorn.com/api/v1/startups?revenue=10k_50k&page=1",
"next": "https://ponicorn.com/api/v1/startups?revenue=10k_50k&page=2",
"prev": null
}
}Can also answer 400, 429. Every failure has the same shape; see errors.
POST /api/v1/startups
Submit a startup for review
Creates a pending submission attributed to the key's owner. It never publishes. Send content-type: application/json; the body is capped at 64 KB. This endpoint answers no CORS preflight, because a key in browser JavaScript is a leaked key.
Needs an API key. Create one at your dashboard and send it as Authorization: Bearer pon_…. Keep it on a server or in your own machine’s config: a key in browser code is a leaked key, which is why this endpoint answers no cross-origin preflight.
| Name | Type | Meaning |
|---|---|---|
| name | stringrequired | |
| website_url | stringrequired | Absolute http(s) URL of the product's own site. |
| description | stringrequired | What the business does, in plain prose. A figure written here must also appear in claim, or the row is flagged. |
| founders | string[]required | Real people. Never an LLC, a studio, or an App Store seller name. |
| category_slugs | string[] | |
| technology_slugs | string[] | |
| business_model_slugs | string[] | |
| growth_channel_slugs | string[] | |
| claim | object | Optional current monthly revenue. Recorded at low confidence until a reviewer or a payment platform says otherwise. |
| note | string | Context for the reviewer. Never shown publicly. |
Request body
{
"name": "Example",
"website_url": "https://example.com",
"description": "What the business does.",
"founders": [
"Ada Lovelace"
],
"business_model_slugs": [
"saas"
],
"category_slugs": [
"developer-tools"
],
"technology_slugs": [
"typescript"
],
"claim": {
"value": "12000",
"currency": "USD",
"source_url": "https://example.com/six-months-in"
},
"note": "I am the founder."
}202 — Filed for review. Nothing is published by this call.
{
"data": {
"submission_id": 412,
"status": "pending_review",
"message": "Filed for review. A person reads every submission before anything is published."
}
}Can also answer 401, 422, 429. Every failure has the same shape; see errors.
GET /api/v1/startups/{slug}
One published startup in full
Every dated figure with the source behind it. A figure the site marks disputed comes back marked.
| Name | Type | Meaning |
|---|---|---|
| slug | stringrequired | The startup's slug, e.g. example. |
200 — The startup, its figures and their sources.
{
"data": {
"name": "Example",
"slug": "example",
"url": "https://ponicorn.com/startups/example",
"tagline": "What the business does, in one line.",
"website_url": "https://example.com",
"logo_url": null,
"founded_year": 2024,
"country_code": "PT",
"team_size": 1,
"funding_type": "bootstrapped",
"status": "active",
"primary_category": "developer-tools",
"founder": {
"name": "Ada Lovelace",
"slug": "ada-lovelace"
},
"founder_verified": true,
"editor_verified": false,
"likes": 34,
"headline_metric": {
"metric": "mrr",
"value": 12000,
"value_text": null,
"currency": "USD",
"measured_at": "2026-08-01",
"period_type": "monthly",
"evidence_status": "reported",
"verified_via": null,
"connected_via": "stripe",
"confidence": "high",
"label": "verified",
"is_current": true,
"verified_at": "2026-08-03",
"disputed": false,
"note": null,
"source": {
"url": "https://example.com/six-months-in",
"title": "Six months in",
"publisher": "Example Blog",
"source_type": "founder_post",
"author": "Ada Lovelace",
"published_at": "2026-08-01"
}
},
"description": "A longer paragraph on what the business does and for whom.",
"ownership": "independent",
"startup_cost": {
"amount": 400,
"currency": "USD",
"note": "Domain and hosting."
},
"launch_time_days": 45,
"launch_time_note": null,
"published_at": "2026-06-02T09:00:00.000Z",
"updated_at": "2026-08-14T11:20:00.000Z",
"founders": [
{
"name": "Ada Lovelace",
"slug": "ada-lovelace",
"role": "Founder",
"is_primary": true,
"website_url": "https://example.com",
"x_url": null,
"linkedin_url": null
}
],
"categories": [
{
"name": "Developer tools",
"slug": "developer-tools"
}
],
"business_models": [
{
"name": "SaaS",
"slug": "saas"
}
],
"technologies": [
{
"name": "TypeScript",
"slug": "typescript"
}
],
"growth_channels": [
{
"name": "Content",
"slug": "content"
}
],
"metrics": [
{
"metric": "mrr",
"value": 12000,
"value_text": null,
"currency": "USD",
"measured_at": "2026-08-01",
"period_type": "monthly",
"evidence_status": "reported",
"verified_via": null,
"connected_via": "stripe",
"confidence": "high",
"label": "verified",
"is_current": true,
"verified_at": "2026-08-03",
"disputed": false,
"note": null,
"source": {
"url": "https://example.com/six-months-in",
"title": "Six months in",
"publisher": "Example Blog",
"source_type": "founder_post",
"author": "Ada Lovelace",
"published_at": "2026-08-01"
}
}
],
"milestones": [
{
"note": "Crossed 12k MRR.",
"published_at": "2026-08-03T08:00:00.000Z",
"metric": {
"metric": "mrr",
"value": 12000,
"value_text": null,
"currency": "USD",
"measured_at": "2026-08-01",
"period_type": "monthly",
"evidence_status": "reported",
"verified_via": null,
"connected_via": "stripe",
"confidence": "high",
"label": "verified",
"is_current": true,
"verified_at": "2026-08-03",
"disputed": false,
"note": null,
"source": {
"url": "https://example.com/six-months-in",
"title": "Six months in",
"publisher": "Example Blog",
"source_type": "founder_post",
"author": "Ada Lovelace",
"published_at": "2026-08-01"
}
}
}
],
"alternatives": [
{
"name": "Other Example",
"slug": "other-example"
}
]
}
}Can also answer 404, 429. Every failure has the same shape; see errors.
GET /api/v1/taxonomy
The slug vocabularies used for filtering
Call this before filtering rather than guessing a slug. Only terms a published startup uses are listed.
200 — Categories, models, stacks and bands.
{
"data": {
"categories": [
{
"name": "Developer tools",
"slug": "developer-tools"
}
],
"business_models": [
{
"name": "SaaS",
"slug": "saas"
}
],
"technologies": [
{
"name": "TypeScript",
"slug": "typescript"
}
],
"funding_types": [
"bootstrapped"
],
"team_sizes": [
{
"slug": "solo",
"label": "Solo founder"
}
],
"revenue_bands": [
{
"slug": "10k_50k",
"label": "$10k–$50k / mo",
"min_monthly_usd": 10000,
"max_monthly_usd": 50000
}
],
"sorts": [
"newest",
"likes",
"revenue",
"name"
],
"all_funding_types": [
"bootstrapped",
"angel",
"venture",
"other"
],
"metric_types": [
{
"slug": "mrr",
"label": "MRR"
}
]
}
}Can also answer 429. Every failure has the same shape; see errors.
GET /api/v1/milestones
Figures founders posted themselves, newest first
Each was posted from a profile its founder claimed, and read by a person before it appeared. Takes page and limit only — the startup filters do not apply here.
| Name | Type | Meaning |
|---|---|---|
| page | integer | 1-based page number.Defaults to 1. |
| limit | integer | Rows per page.Defaults to 25. |
200 — One page of milestones.
{
"data": [
{
"note": "Crossed 12k MRR.",
"published_at": "2026-08-03T08:00:00.000Z",
"startup": {
"name": "Example",
"slug": "example",
"url": "https://ponicorn.com/startups/example"
},
"metric": {
"metric": "mrr",
"value": 12000,
"value_text": null,
"currency": "USD",
"measured_at": "2026-08-01",
"period_type": "monthly",
"evidence_status": "reported",
"verified_via": null,
"connected_via": "stripe",
"confidence": "high",
"label": "verified",
"is_current": true,
"verified_at": "2026-08-03",
"disputed": false,
"note": null,
"source": {
"url": "https://example.com/six-months-in",
"title": "Six months in",
"publisher": "Example Blog",
"source_type": "founder_post",
"author": "Ada Lovelace",
"published_at": "2026-08-01"
}
}
}
],
"page": {
"number": 1,
"size": 25,
"total": 41,
"pages": 2
},
"links": {
"self": "https://ponicorn.com/api/v1/milestones?page=1",
"next": "https://ponicorn.com/api/v1/milestones?page=2",
"prev": null
}
}Can also answer 400, 429. Every failure has the same shape; see errors.
The envelope
Every response carries its payload under data. A listing adds page, which says where you are, and links, which is your own request with the page changed — so paging never means rebuilding a query string. On the last page links.next is null.
Paging through a listing
{
"data": [ … ],
"page": { "number": 1, "size": 25, "total": 63, "pages": 3 },
"links": { "self": "…?page=1", "next": "…?page=2", "prev": null }
}What a figure is
Every number carries the date it was true, the page it was read from, and one of two labels. verified means the founder stands behind the profile, Ponicorn read the number from their connected payment account, or a third party read it from a public dashboard. unverified means it was said in public, estimated or inferred — and it still carries its source and its date. Quote the label; never present an estimate as a confirmed fact. See how figures work.
One figure, in full
{
"metric": "mrr",
"value": 12000,
"value_text": null,
"currency": "USD",
"measured_at": "2026-08-01",
"period_type": "monthly",
"evidence_status": "reported",
"verified_via": null,
"connected_via": "stripe",
"confidence": "high",
"label": "verified",
"is_current": true,
"verified_at": "2026-08-03",
"disputed": false,
"note": null,
"source": {
"url": "https://example.com/six-months-in",
"title": "Six months in",
"publisher": "Example Blog",
"source_type": "founder_post",
"author": "Ada Lovelace",
"published_at": "2026-08-01"
}
}Errors
One shape for every failure, on every endpoint, so a caller writes one error branch. details appears only when the request named fields.
The shape of an error
{ "error": { "code": "not_found",
"message": "No published startup has that slug." } }| Code | HTTP | When |
|---|---|---|
| bad_request | 400 | page or limit was not a positive whole number. |
| unauthorized | 401 | No API key was sent, or it is not a key. |
| invalid_key | 401 | The key does not exist. |
| key_revoked | 401 | The key existed and was revoked. |
| not_found | 404 | Nothing published has that slug. |
| method_not_allowed | 405 | The path exists; that verb does not. |
| unsupported_media_type | 415 | A write without content-type: application/json. |
| payload_too_large | 413 | The body is over the size cap. |
| unprocessable | 422 | The submission is well-formed but a field is wrong. |
| rate_limited | 429 | Too many requests. Wait the retry-after seconds. |
| internal | 500 | Something failed on our side. |
Limits and caching
Reads are limited to 120 requests a minute per address. Writes are limited to 10 a minute per key, 5 a minute per account and 20 submissions a day per account. A refusal is a 429 carrying retry-after: 60. A body over 64 KB is refused unread.
| Response | cache-control |
|---|---|
| One startup. | public, max-age=300, s-maxage=3600, stale-while-revalidate=86400 |
| A search or the milestone feed. | public, max-age=60, s-maxage=300, stale-while-revalidate=3600 |
| The taxonomy and the OpenAPI document. | public, max-age=3600, s-maxage=86400 |
Reads answer any origin. The write answers none: it takes a bearer token, and a token that reaches browser JavaScript has leaked. Every endpoint sends x-robots-tag: noindex, nofollow, so the JSON never competes with the pages.
Other ways in
The same capabilities are available as an MCP server for agents — one core behind both, so they cannot disagree. Every public page also has a Markdown version at the same URL with .md appended; /llms.txt lists the site for a model, and /sitemap lists it for a person.