Readable endpoint index
GET /api/v1/products/{slug}/trafficQuery product traffic
Organization members and admins only. From/to are UTC ISO timestamps; defaults to the last 30 days, maximum 90 days, no future timestamps. Counts NLB product page traffic, not the external product website. Visitors rotate daily; summed across days they are not unique people. Top 10 referrers, countries and devices describe page views. Active visitors is always the current 5-minute window, independent of from/to.
GET /api/v1/products/{slug}/traffic/publicRead published Analytics block totals
Available only when the latest public document (approved or published) contains an Analytics block. Draft changes do not change this opt-in. Returns totals and daily series; referrers/countries/devices are private.
GET /api/v1/blog/blocksList blog block types
POST /api/v1/blog/validateValidate a blocks array
GET /api/v1/blog/postsList blog posts
Published posts are public. status=draft|archived|all requires an admin caller.
POST /api/v1/blog/postsCreate a draft post
GET /api/v1/blog/posts/{id}Get a post by id or slug
`{id}` may be the post id or its slug. Published posts are public; drafts and archived posts are returned to admins only (404 otherwise).
PATCH /api/v1/blog/posts/{id}Update a post
DELETE /api/v1/blog/posts/{id}Archive a post
POST /api/v1/blog/posts/{id}/publishPublish a post
Sets publishedAt on first publish and keeps it on republish.
POST /api/v1/blog/posts/{id}/unpublishUnpublish a post (back to draft)
GET /api/v1/blog/posts/{id}/revisionsList revisions (newest first, no snapshot)
GET /api/v1/blog/posts/{id}/revisions/{revisionId}Get a revision with its snapshot
POST /api/v1/blog/posts/{id}/revisions/{revisionId}/restoreRestore a revision as a new version
Content is restored; locale and status are unchanged.
GET /api/v1/builder-projectsList my Builder League projects
POST /api/v1/builder-projectsCreate a project
Optionally links a directory product you own (organization owner or admin). Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
GET /api/v1/builder-evidenceList my evidence
Newest first, with the latest appeal of each item.
POST /api/v1/builder-evidenceSubmit evidence for a ruleset event
Evidence is private by default and waits for a moderator (or the named counterparty, for peer help). The source is set from the caller: session `member`, API key `api`, MCP delegation `agent`. Callers can never set a status. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
GET /api/v1/builder-evidence/{id}Get evidence
Readable by its owner, admins and the named counterparty (without files).
PATCH /api/v1/builder-evidence/{id}Edit or withdraw evidence
Only while `submitted` or `needs_info`; answering a `needs_info` request puts it back in the queue. Send `{ "action": "withdraw" }` to withdraw. HTML forms use POST on the same path. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
GET /api/v1/builder-evidence/{id}/files/{key}Download a private evidence file
Owner and admins only; everyone else gets 404. Served with `Cache-Control: private, no-store`.
POST /api/v1/builder-evidence/{id}/appealAppeal a rejection
One appeal per rejection, never two open at once. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
POST /api/v1/builder-evidence/{id}/confirmConfirm peer help as the counterparty
Signed-in session only (API keys and agents get 403). Self-confirmation, accounts sharing a Discord identity and circular confirmations beyond the ruleset cap are refused. When the ruleset accepts peer confirmation the evidence is approved and RP awarded; otherwise it stays in the moderator queue. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
GET /api/v1/builder-admin/queueModeration queue
Admin session only. Oldest first. `view=appeals` lists open appeals instead.
POST /api/v1/builder-admin/evidence/{id}/decisionApprove, reject or request information
Admin session only; API keys and agents can never approve, and nobody decides on their own evidence. Approval records a moderator-verified event and awards RP exactly once, even under concurrent approvals. Every decision is audited. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
POST /api/v1/builder-admin/evidence/{id}/appealUphold or overturn an appeal
Admin session only. Overturning approves the evidence and awards RP. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
POST /api/v1/builder-admin/ledger/{entryId}/reverseReverse a ledger entry
Admin session only. Adds a negative entry and an audit row; idempotent per entry. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
GET /api/v1/builder-admin/pilotList pilot members
POST /api/v1/builder-admin/pilotAdd or remove a pilot member
Admin session only; audited and idempotent. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
POST /api/v1/builder-admin/backfillBackfill directory launches
Admin session only. For members who opted in, imports each published product they own as a system-verified SHIP launch at its original approval date, scored by the ruleset in force then. Dry run unless `dryRun: false`; idempotent; never creates activity that did not happen. Also accepts `application/x-www-form-urlencoded` or `multipart/form-data` from a same-site HTML form: without `Accept: application/json` the response is a 303 redirect to `returnTo` (a Builder League path) with `?notice=` or `?error=`.
GET /api/v1/builder-accessShould the menu show the Builder League?
Always 200. `showNav` is true while the Builder League is open to members, or when the caller may use it (admin, pilot member). Signed-out callers get the public answer. Used by the site menu once per browser tab.
POST /api/v1/builder-admin/ingest/discordIngest a Discord bot event as evidence
For the community Discord bot. Authenticate with the shared `x-nlb-builder-ingest-secret` header or an admin's API key minted with the `builder:ingest` scope (`POST /api/v1/api-keys` with `scopes: ["builder:ingest"]`). Admin sessions also work; full API keys and MCP delegation get 403, and a key with another scope gets 401. Only these kinds count, never message volume: `ship_post` → `ship_public_post`, `help_confirmation` → `contribute_peer_help`, `weekly_active` → `contribute_discord_active`. The member is resolved from the Discord account linked during onboarding. The evidence is stored as `submitted` with source `discord` and waits for a moderator; this route never approves or awards. Idempotent per member and `messageId` (client key `discord:{messageId}`): a retry answers 200 with the stored evidence. `help_confirmation` needs `counterpartDiscordUserId` (the helped member), who still confirms from their own account. SHIP evidence needs a project, which the member attaches before review.
GET /api/v1/builder-meRead your Builder League summary
Level, rank checklist, this week's League Score, pending evidence and privacy switches. Requires `builder:read`.
PATCH /api/v1/builder-meUpdate your Builder League privacy
Every public surface is opt-in and private by default. Consent changes need a signed-in browser session; API keys and agents get 403.
GET /api/v1/builder-rankingsRead a builder board
Boards rank verified ledger RP per period in Asia/Ho_Chi_Minh time. Only members who opted in are listed; the caller's own position is returned in `viewer`. Ties break on verified events, then the earlier time the score was reached, then user id.
GET /api/v1/builder-passport/{userId}Read a Builder Passport
The member and admins get the private view. Everyone else gets the public view, and only when the member opted in; otherwise 404. While the league is in pilot only admins and pilot members can read passports.
POST /api/v1/builder-admin/nominationsNominate a member for a rank
Admin browser session; the admin is the mentor. One nomination per mentor, member, rank and calendar year. Legend needs at least two. The rank gate is re-evaluated after the nomination.
POST /api/v1/builder-admin/snapshots/finalizeFinalize league snapshots
Admin browser session. With `periodType` and `periodKey`, finalizes that period's boards (409 before the period end plus grace). With an empty body, finalizes every due period like the cron does. Final snapshots are immutable; later changes append a revision with `supersedesId`.
GET /api/v1/builder-badgesList badges with your awards
The full badge catalogue joined with the caller's standing awards. Pending awards are evaluated first (idempotent). Badges come only from verified evidence; paying, logging in and posting volume never award one.
GET /api/v1/builder-missionsSuggested missions
Stage-aware suggestions from the caller's verified history. A verified stop/pivot starts a new idea cycle at VALIDATE. No streaks, no deadlines; each mission links to the evidence form.
GET /api/v1/builder-seasonsCurrent season and your check-ins
Eight-week season (Vietnam time), the caller's optional enrollment, a weekly check-in of verified progress and the caller's placements in finalised past seasons.
POST /api/v1/builder-seasonsJoin or update a season
Enrolls the caller in the current or next season with a track and an optional goal. Tracks never change scoring.
GET /api/v1/builder-rewardsRewards you can work towards
Active rewards that are open or upcoming, with per-requirement eligibility and the caller's claim for the current period, plus the caller's claim history. Rewards are opportunities, never cash; RP is never spent and membership tier never matters.
POST /api/v1/builder-rewards/{id}/claimRequest a reward
One request per reward and period. Signed-in session only. Inventory is reserved atomically; a moderator reviews every request.
GET /api/v1/builder-time-capsuleYour Time Capsule for a year
Latest intent and reflection plus every earlier version (owner only).
POST /api/v1/builder-time-capsuleSave a Time Capsule entry
Appends a new version of the intent or reflection for last year, this year or next year. Private by default.
GET /api/v1/builder-admin/rewards/itemsList reward catalogue
POST /api/v1/builder-admin/rewards/itemsCreate or update a reward
Upsert by key. Eligibility may only use rank, badge, season placement or verified milestones. Audited.
POST /api/v1/builder-admin/rewards/seedAdd the initial reward catalogue
Idempotent. Adds missing items as inactive and leaves existing keys untouched. Audited.
GET /api/v1/builder-admin/rewards/claimsList reward claims
POST /api/v1/builder-admin/rewards/claims/{id}/decisionDecide a reward claim
approve, reject, redeem, expire or cancel. Rejecting, expiring or cancelling returns the place to inventory exactly once. Audited.
POST /api/v1/builder-admin/rewards/badges/{awardId}/revokeRevoke a badge award
Needs a reason. The award row stays with the reason so evaluation never re-grants it. Audited and recorded on the member's journey. Idempotent.
GET /api/v1/builder-journeyRead my builder journey
The caller's journey, oldest first: verified and pending activity, corrections and reversals (each its own item, linked to what it supersedes), backfilled history labelled with its provenance, rank and badge awards. Keyset-paginated.
GET /api/v1/builder-year/{year}Read my Year in Review
The latest snapshot of the caller's year. Provisional until Dec 30 23:59 (UTC+7) has closed and while late approvals may land; final (immutable) after Dec 31 plus the moderation grace period. `review` is private; `shared` is exactly what a share link and the video show.
PATCH /api/v1/builder-year/{year}Hide scenes from my shared year
Replaces the list of scenes kept out of the shared version and video. Session only: API keys and agents are refused.
GET /api/v1/builder-year/{year}/shareList my Year in Review share links
POST /api/v1/builder-year/{year}/shareCreate a share link
Returns the personal URL (with UTM tags) once; only a hash of its token is stored. At most 5 active links per year. Session only.
DELETE /api/v1/builder-year/{year}/share/{id}Revoke a share link
Idempotent. The link stops resolving immediately.
GET /api/v1/builder-year/{year}/renderList my Year in Review video renders
POST /api/v1/builder-year/{year}/renderRequest a Year in Review video
Queues a 30–60 second video of the shared scenes. Idempotent per snapshot and format; a failed or expired render is queued again. Outputs are private and kept 30 days.
GET /api/v1/builder-year/{year}/render/{jobId}/downloadDownload my rendered video or poster
GET /api/v1/builder-wrapped/{year}Community Wrapped
Public aggregate of the community's year. Every metric names its source, event types, verification levels, timeframe and denominator. Cohorts of fewer than 5 members are suppressed; no member is named.
POST /api/v1/builder-wrapped/visitRecord a shared Year in Review visit
Beacon from a shared page. Sets a random first-party visitor cookie, stored only as a daily HMAC, plus the share reference used for signup attribution. No IP or user agent is stored. Clients must not call it when DNT or GPC is set.
GET /api/v1/builder-admin/render-jobsList render jobs (admin)
Auth: an admin browser session, or an admin's API key minted with the `builder:render` scope (`POST /api/v1/api-keys` with `scopes: ["builder:render"]`). Full API keys and MCP delegation get 403; a key with another scope gets 401.
POST /api/v1/builder-admin/render-jobs/claimLease the next render job (renderer)
Leases the oldest due job for 15 minutes and returns the public-safe scenes. `lease` must be sent back on complete or fail; a renderer whose lease expired is fenced out. Auth: an admin browser session, or an admin's API key minted with the `builder:render` scope (`POST /api/v1/api-keys` with `scopes: ["builder:render"]`). Full API keys and MCP delegation get 403; a key with another scope gets 401.
POST /api/v1/builder-admin/render-jobs/{id}/completeUpload a finished render (renderer)
Auth: an admin browser session, or an admin's API key minted with the `builder:render` scope (`POST /api/v1/api-keys` with `scopes: ["builder:render"]`). Full API keys and MCP delegation get 403; a key with another scope gets 401.
POST /api/v1/builder-admin/render-jobs/{id}/failReport a failed render (renderer)
Re-queues with exponential backoff, or fails permanently after 5 attempts or when `permanent` is true. Auth: an admin browser session, or an admin's API key minted with the `builder:render` scope (`POST /api/v1/api-keys` with `scopes: ["builder:render"]`). Full API keys and MCP delegation get 403; a key with another scope gets 401.
GET /api/v1/builder-admin/privacy/{userId}/exportExport a member's Builder League data (admin)
Everything the league stores about the member as one JSON download: privacy switches, pilot entry, projects, evidence (file keys only), appeals, events, ledger, badges, ranks, seasons, reward claims, Time Capsule, yearly snapshots, share links (never tokens) and their funnel counts, render jobs and the decision history without moderator ids. Each export is recorded in the moderation audit trail.
POST /api/v1/builder-admin/privacy/{userId}/deleteDelete a member's Builder League data (admin)
Irreversible. Deletes the member's private R2 files (evidence and rendered videos) first, then in one atomic D1 batch deletes evidence, appeals, privacy switches, pilot entry, projects, badges, ranks, nominations, season entries, reward claims (open claims give their unit back), Time Capsule notes, yearly snapshots, render jobs, share links and their attribution rows. Events, ledger entries, cap slots, audit rows and stored board entries are kept with the member's id replaced by a fresh tombstone id, so finalized boards still reproduce. The account itself is not deleted. Idempotent: a repeat reports `nothingToDo`. The body must repeat the member id and be sent as JSON. Admins cannot delete their own data.
GET /api/v1/builder-me/exportExport my Builder League data
The caller's own league data, in the same format as the admin export, as one JSON download. Works with a session, a full (unscoped) API key or MCP delegation with `mcp:read`; it includes the caller's private evidence and never returns another member's data. Each export is recorded in the moderation audit trail.
POST /api/v1/builder-me/deleteDelete my Builder League data
Irreversible. Runs the admin delete routine on the caller's own data only: private files and personal rows are deleted, history is pseudonymised with a fresh tombstone id, and the account itself is kept. Signed-in session only: API keys and MCP agents are refused, so an agent can never erase a member's history. The body must be JSON and confirm with the word `DELETE`. Admins are refused and must ask another admin. Idempotent: a repeat reports `nothingToDo`. The audit row names only the tombstone.
GET /api/v1/operator/approvalsList approvals of a domain
Newest first, with the canonical `args` behind `argsHash`. `?status=` pending (default, not expired), approved, rejected, consumed or expired.
POST /api/v1/operator/approvalsRequest an approval
For a sensitive action of `?domain=`. A different operator must approve it; it expires after `ttlMinutes` (default 30) and is consumed once by the requester. `target` lists exactly the arguments the action runs with: `operator.grant` and `operator.revoke` take `{ userId, capability }`, `courses.video.archive` `{ slug }`, `users.user.ban` `{ userId, reason, expiresAt }`, `membership.entitlement.grant` `{ userId, featureKey, validUntil }`, `membership.entitlement.revoke` `{ grantId }`, `commerce.order.refund` `{ orderId, reason, amount }`, `builder_league.ledger.reverse` `{ entryId, reason }`, `builder_league.privacy.delete` `{ userId }` (null fields included).
GET /api/v1/operator/approvals/{id}Get an approval
Action, resource, canonical `args`, reason, requester and effective status. Approvals of another domain answer 404.
POST /api/v1/operator/approvals/{id}/decisionDecide an approval
Approve or reject; never one's own request.
GET /api/v1/operator/system/auditRead the whole operator audit trail
Every domain, newest first; `?resource=` exact resource string. Needs `system:manage`.
GET /api/v1/operator/system/operatorsList operator grants
Live grants first, newest first. Needs `system:manage`.
POST /api/v1/operator/system/operatorsGrant an operator capability
Sensitive (`operator.grant`) and session-only: an MCP delegation is refused with `session_required`.
POST /api/v1/operator/system/operators/revokeRevoke an operator capability
Sensitive (`operator.revoke`) and session-only. `not_granted` (409) when the user does not hold the capability.
GET /api/v1/operator/courses/videosList Courses videos
Every status; `?status=` filters. Needs `courses:manage`.
POST /api/v1/operator/courses/videosCreate a draft video
The Polar product is created on publish. `slug_taken` (409).
GET /api/v1/operator/courses/videos/{slug}Get a Courses video
Any status, with the Stream uid and Polar product.
PATCH /api/v1/operator/courses/videos/{slug}Update a Courses video
Partial. `status` may only be `draft`: archiving needs the approval-gated archive route. `price_locked` (409) once the Polar product exists; `stream_uid_required` (422) when clearing the uid of a published video.
POST /api/v1/operator/courses/videos/{slug}/publishPublish a Courses video
Enforces signed-only Stream playback and creates the Polar product (idempotent). `stream_uid_required` (422) without a uid.
POST /api/v1/operator/courses/videos/{slug}/archiveArchive a Courses video
Sensitive (`courses.video.archive`, target `{ slug }`): hidden from new buyers, existing buyers keep watching. Needs an approval decided by a different operator.
GET /api/v1/operator/assetsList uploaded assets
Every organization, newest first. Needs `assets:manage`.
POST /api/v1/operator/assets/{id}/promotePromote an asset
Staging only (`asset_not_staging`, 409). A promoted asset is public (`asset:<id>` download for anyone).
POST /api/v1/operator/assets/{id}/rejectReject an asset
Staging only (`asset_not_staging`, 409); the asset stays private to its organization.
GET /api/v1/operator/directory/productsList directory products
Every organization and status (`?status=`). Needs `directory:manage`.
GET /api/v1/operator/directory/products/{id}Get a directory product
Any status, with its current revision and its 20 most recent submissions and their moderation case.
GET /api/v1/operator/directory/moderationModeration queue
Cases with product and revision, highest priority first; `?status=` in_review (default), open or decided.
POST /api/v1/operator/directory/moderation/{caseId}/decisionDecide a moderation case
Session only (`session_required` for MCP delegations): a named human records the decision; `approved` publishes the product. `case_decided` (409) when already decided.
POST /api/v1/operator/directory/rankingsCompute a leaderboard snapshot
Freezes the directory ranking for a window; `/api/v1/leaderboards` serves the newest snapshot.
GET /api/v1/operator/usersSearch accounts
Newest first; `?q=` matches the id exactly or an email/name prefix. Needs `users:manage`.
GET /api/v1/operator/users/{id}Get an account
With the effective membership tier, operator capabilities and organizations.
POST /api/v1/operator/users/{id}/banBan an account
Sensitive (`users.user.ban`, target `{ userId, reason, expiresAt }`): every credential is refused at the next check and open sessions end. `expiresAt` null bans without an end. `cannot_ban_self` (409).
POST /api/v1/operator/users/{id}/unbanLift a ban
No approval needed; the audit row records the operator.
GET /api/v1/operator/users/orgsSearch organizations
Newest first; `?q=` matches a slug or name prefix.
GET /api/v1/operator/users/orgs/{id}Get an organization
With its members and their roles.
GET /api/v1/operator/membership/users/{userId}Get a user's access
Effective membership tier, entitlements written by purchases, and manual grants (revoked ones included). Needs `membership:manage`.
POST /api/v1/operator/membership/resyncResync memberships from Polar
Re-reads live membership subscriptions (oldest-synced first) and queues access reconciles for changed users.
POST /api/v1/operator/membership/grantsGrant an entitlement by hand
Sensitive (`membership.entitlement.grant`, target `{ userId, featureKey, validUntil }`). Stored as a grant, never as an order or purchase, so refunds and grants never cancel each other. `grant_exists` (409) while a live grant for the key exists.
POST /api/v1/operator/membership/grants/{id}/revokeRevoke a manual grant
Sensitive (`membership.entitlement.revoke`, target `{ grantId }`). The row stays as history. `grant_revoked` (409).
GET /api/v1/operator/commerce/ordersList orders
Stored Polar orders, newest first; `?userId=`, `?status=`. Needs `commerce:manage`.
GET /api/v1/operator/commerce/orders/{id}Get an order
With the customer email and linked user.
POST /api/v1/operator/commerce/orders/{id}/refundRefund an order through Polar
Sensitive (`commerce.order.refund`, target `{ orderId, reason, amount }`; `amount` null refunds everything still refundable; the approval also binds the stored Polar order id and currency). Answers 202: Polar's `order.refunded` webhook updates the order and revokes what it paid for. `already_refunded`, `refund_amount_too_high` (409), `refund_failed` (502).
GET /api/v1/operator/commerce/subscriptionsList subscriptions
Stored Polar subscriptions, newest first; `?userId=`, `?customerId=`, `?status=`.
GET /api/v1/operator/commerce/subscriptions/{id}Get a subscription
With the linked user.
POST /api/v1/operator/commerce/subscriptions/{id}/cancelCancel a subscription at period end
Asks Polar to end the subscription when the current period ends and stores the state Polar answers with; access lasts until then. `subscription_ended`, `already_canceling` (409), `subscription_update_failed` (502).
POST /api/v1/operator/commerce/subscriptions/{id}/resumeResume a subscription
Undoes a pending cancel through Polar. `subscription_ended`, `not_canceling` (409), `subscription_update_failed` (502).
GET /api/v1/operator/commerce/customersList customers
Polar customers, newest first; `?userId=` only, so no email travels in a URL.
GET /api/v1/operator/commerce/customers/{id}Get a customer
With their stored orders and subscriptions.
GET /api/v1/operator/membership/entitlementsList purchased entitlements
Newest first; `?userId=`, `?featureKey=`, `?all=true` includes inactive ones. Manual grants are on `…/membership/users/{userId}`.
GET /api/v1/operator/commerce/webhooksList problem webhook deliveries
Polar deliveries that failed (`?status=failed`, default) or are unprocessed (`pending`). Payloads are never returned.
GET /api/v1/operator/builder-league/queueList the moderation queue
Evidence awaiting review, oldest first, with a keyset `cursor`. `?view=appeals` lists open appeals instead. Needs `builder_league:manage`.
POST /api/v1/operator/builder-league/evidence/{id}/decisionDecide evidence
Approve, reject or ask for more information. Session only; never on one's own evidence. Approving is idempotent and returns the award.
POST /api/v1/operator/builder-league/ledger/{entryId}/reverseReverse a ledger entry
Sensitive (`builder_league.ledger.reverse`, target `{ entryId, reason }`). Writes a negative entry; idempotent per entry.
GET /api/v1/operator/builder-league/rewards/claimsList reward claims
Newest first; `?status=` filters.
POST /api/v1/operator/builder-league/rewards/claims/{id}/decisionDecide a reward claim
A freed place returns to inventory.
GET /api/v1/operator/builder-league/rewards/itemsList reward items
Every catalogue item, active or not.
POST /api/v1/operator/builder-league/rewards/itemsCreate or update a reward item
Upsert by `key`; also recorded in the Builder League audit.
GET /api/v1/operator/builder-league/privacy/{userId}/exportExport a member's Builder League data
Everything the Builder League stores about one member.
POST /api/v1/operator/builder-league/privacy/{userId}/deleteDelete a member's Builder League data
Sensitive and irreversible (`builder_league.privacy.delete`, target `{ userId }`): deletes the data and pseudonymises the history. `self_delete` (409).
POST /api/v1/operator/community/reconcileQueue a community access reconcile
Applies the user's current membership to each target. Answers 202. Needs `community:manage`.
GET /api/v1/operator/system/jobsBackground job health
Counts per status and kind, dead jobs and pending jobs that already failed; payloads are never returned. Needs `system:manage`.
POST /api/v1/operator/system/jobs/{id}/retryRetry a job
Back in the queue with fresh attempts. `not_retryable` (409) when missing or running.
GET /api/v1/courses/libraryMy Courses library
Every published video with `canWatch` decided by `video:<id>` watch (purchase, entitlement or a qualifying plan). `?owned=true` keeps only watchable videos.
GET /api/v1/courses/videos/{slug}A Courses video with my access
Published or archived video; `canWatch` is false for anonymous callers.
GET /api/v1/assetsAssets of my organization
Needs `org:<id>` read (organization membership). Newest first.
GET /api/v1/assets/{id}One asset
`asset:<id>` download: a promoted asset for anyone, any other asset for members of the owning organization. A missing asset is `not_available` (403).
GET /api/v1/directory/mineMy directory products
Products of every organization I belong to, any status; `?status=` filters.
GET /api/v1/leaderboardsLatest directory leaderboard
The newest frozen snapshot for `?window=` (daily default); `data` is null before the first snapshot. Builder League boards are at `/api/v1/builder-rankings`.
GET /api/v1/onboarding-statusRead my membership onboarding status
The caller's onboarding state: payment, membership (tier, plan, term, renewal), member profile and Discord/Telegram connection status with the next action for each. Read-only: it never verifies a Polar checkout (the onboarding page does). API keys, OAuth MCP delegation (`mcp:read`) and sessions are accepted. Returns 404 while membership onboarding is not enabled on the environment.
GET /api/v1/products/{slug}/pulseRead product website health history
Read saved samples for a published product with a Pulse block. No probe is performed. Uptime is the percentage of successful measured samples, not continuous availability; unknown samples are excluded. History contains at most 100 samples and becomes stale after 15 minutes.
POST /api/v1/products/{slug}/pulseSample the product's published website
Send an empty body. Checks the saved website URL, never a caller-supplied URL. A five-minute per-product cooldown and global concurrent-probe limit apply. Returns sampled=false when a new probe is not due or capacity is full. Probes are on-view, not scheduled continuous monitoring.
GET /api/v1/statsDirectory statistics
POST /api/v1/media/uploadUpload product media
Upload an image (up to 10 MB) or video (up to 100 MB). The current endpoint accepts draft creators without a session.
GET /api/v1/api-keysList your API keys
Returns key metadata only, never the raw secret. `scopes` lists a key's narrow scopes, or is null for a full key.
POST /api/v1/api-keysCreate an API key
Requires a browser session. The raw key is returned only once; store it securely. Admins may bind a key to narrow automation `scopes` (`builder:render` for the Year in Review render worker, `builder:ingest` for the Discord bot); a scoped key authenticates only on the routes that accept its scope and nowhere else. Without `scopes` the key is a full key, as before.
DELETE /api/v1/api-keys/{id}Revoke your API key
GET /api/v1/admin/queueRead moderation queue
Admin browser session required.
POST /api/v1/admin/decideRecord a human moderation decision
Admin browser session required. Reviewer identity is derived from the session.
GET /api/v1/productsList Published Directory Products
Retrieve a paginated list of human-verified and published products ordered by trust score and rank.
POST /api/v1/productsCreate Product Draft
Initialize a new product draft under an organization. Requires a session or API key and organization membership.
GET /api/v1/products/{slug}Get Product by Slug
Retrieve public verified product listing and its active revision document.
POST /api/v1/products/{slug}/revisionsCreate Product Document Revision
Save a new block-based document revision. Validates against the official block schema contract (including Activities, Pulse and Analytics). Publishing an Analytics block opts into public traffic totals for that product.
POST /api/v1/products/{slug}/submitSubmit Product Revision for Review
Submit a revision for automated pre-screening and board moderation.
GET /api/v1/products/{slug}/markdownExport Product as LLM Markdown
Markdown structured feed formatted for AI agents and LLM context windows.
GET /api/v1/rankingsGet Community Rankings
Fetch transparent daily, weekly, or monthly organic community leaderboard rankings.
POST /api/v1/votesCast Vote for Product
Cast an organic community vote for a product in the current 24-hour UTC window. One vote per product per user per day.
GET /api/healthService & Database Health Probe
Live health status check across Cloudflare Workers runtime and Hyperdrive PostgreSQL.