API Overview
The Passport API gives you read access to organizations, events, hackathons, bounties, and builder profiles.
Two surfaces
Passport exposes two HTTP surfaces, and they do not serve the same data.
/api/v1/* — the integrator API. Versioned, key-authenticated, paginated,
and the one you should build against. Its event endpoints serve the
Luma-synced calendar.
/api/* — internal. Unversioned and unauthenticated. It backs Passport’s
own front ends, including this website’s homepage. It carries no stability
guarantee and can change without notice. It is documented in
Endpoints for reference, not for you to depend on.
Its event catalogue is a mix: most events are synced from Luma and carry a
luma_url, and those live on Luma rather than having a page on Passport.
Events without a luma_url are hosted on Passport itself. If you are linking
to an event, follow that field — it is the only reliable signal of where the
event actually lives.
Base URL
https://demo.stellarpassport.xyz/api/v1Getting an API key
Keys are minted per organization from the Passport admin panel under Admin → API Keys. If your organization does not have admin access yet, get in touch and we will set one up.
Keys look like pk_ followed by 48 hexadecimal characters.
Authentication
Send your key as a bearer token on every request:
Authorization: Bearer pk_your_key_hereThe one exception is GET /api/v1/organizations/[slug]/page, which is public
and needs no key.
A missing or invalid key returns 401:
{ "error": "Invalid or missing API key" }See Authentication for details.
Pagination
Every v1 list endpoint uses the same cursor pagination.
| Parameter | Description |
|---|---|
limit | Results per page. Default 50, maximum 100. |
cursor | Opaque cursor. Pass the next_cursor from the previous response. |
Responses carry has_more and next_cursor. On the last page next_cursor
is null. A cursor looks like 2026-08-20T16:11:49.159Z|68 — treat it as
opaque and pass it back unmodified.
{
"hackathons": [ ... ],
"has_more": true,
"next_cursor": "2026-08-20T16:11:49.159Z|68"
}A non-numeric limit returns 400 {"error": "Invalid limit"} and an
unparseable cursor returns 400 {"error": "Invalid cursor"}. A limit
above the maximum is clamped rather than rejected.
One exception: GET /api/v1/organizations does not follow this contract.
It ignores limit and cursor and reports a total instead of has_more /
next_cursor. It is also filtered — see below.
Errors
Errors return a JSON body with an error field and an appropriate status:
{ "error": "Invalid or missing API key" }| Status | Meaning |
|---|---|
400 | Invalid parameter — a bad limit, cursor, or status value |
401 | Missing or invalid API key |
404 | No such resource |
Rate limits
There are no published rate limits, and responses currently return no rate-limit headers. Be a good citizen: cache what you can and avoid tight polling loops. If you need high volume, mention it when you request your key.