Overview
Base URL: /api/v1 on this site. All responses are JSON. Dates are ISO 8601 strings. The API is served by the same Cloudflare Worker as the website; there is no separate host to configure.
Public endpoints return approved listings only. A listing that is pending, rejected or suspended is not discoverable, and its detail endpoint answers 404 unless you are its owner or a moderator.
This is a small, honest API: there are no invented popularity scores. featured and hiddenGem are editorial flags set by moderators, and verificationStatus means a moderator checked the developer identity — not that the bot is safe.
Authentication
Public reads (/discover, /featured, /categories, listing detail) need no credentials. Authenticated write endpoints accept either:
- a session cookie from the website sign-in (used by the developer dashboard), or
- an API key as
Authorization: Bearer mb_live_….
A key belongs to one application, which belongs to one account. The only scope used today is submissions:write; scopes are assigned by the server and cannot be requested by clients. Keys are stored hashed and shown once at creation. Revoking a key takes effect on the next request.
curl -H "Authorization: Bearer mb_live_1a2b3c4d_…" \
https://meros-basement-web.site-access.workers.dev/api/v1/submissions/mineErrors
Every v1 error uses one envelope:
{ "error": { "code": "validation_error", "message": "Summary must be 20–200 characters." } }| Status | Meaning |
|---|---|
| 400 | Invalid input or unknown filter |
| 401 | Missing, invalid or revoked credentials |
| 403 | Authenticated but not allowed (role, scope or suspended account) |
| 404 | Not found, or not visible to you |
| 409 | Conflict: duplicate name/slug, or a state that cannot change |
| 413 / 415 | Body too large, or not application/json |
| 429 | Rate limited — respect Retry-After |
| 503 | Database not configured or temporarily unreachable |
Legacy endpoints (/api/live, /api/ratings, /api/leads) keep their original shapes for compatibility and are documented separately.
Public endpoints
/api/v1/discoverPaginated search over approved listings.
| Query | Type | Notes |
|---|---|---|
| q | string | Name/summary substring, max 80 characters |
| category | string | One of the category ids below |
| tag | string | Single tag, lowercase, max 24 characters |
| gem | true | Only editorial hidden gems |
| sort | enum | new (default) | name | oldest |
| page | integer | ≥ 1, default 1 |
| perPage | integer | 1–50, default 20 |
meta.hasMore tells you whether another page exists. There is no total count, because an exact count is not worth a second scan for a directory this size.
{ "data": [ { "id": "…", "slug": "…", "name": "…", "summary": "…", "category": "utility", "tags": ["moderation"], "status": "approved", "featured": false, "hiddenGem": false, "verificationStatus": "unverified", "inviteUrl": "https://…", "developer": { "username": "…", "displayName": "…", "avatarUrl": null } } ], "meta": { "page": 1, "perPage": 20, "hasMore": false } }/api/v1/featuredUp to 12 approved listings where moderators set featured. Not paid placement.
/api/v1/categoriesCategory ids, labels, descriptions and sort order. Use these ids for filters.
{ "data": [ { "id": "utility", "label": "Utility", "description": "…", "sort_order": 10 } ], "meta": { "perPage": 9 } }/api/v1/bots/{id-or-slug}One listing by UUID or slug. Includes description, links and the public developer profile. 404 if the listing is not approved, unless you own it or are a moderator.
/api/v1/bots/{id}/reportsReport an approved listing. No account is required. Rate limited by IP.
| Field | Type | Notes |
|---|---|---|
| reason | enum | spam | malicious | impersonation | broken | other |
| details | string | Optional, max 2000 characters |
The response is 202 with { "ok": true }. Reports go to a private moderation queue and never change the listing automatically.
Submissions
Requires submissions:write (API key) or a browser session. Owners are always the authenticated account; a key cannot submit for someone else.
/api/v1/submissions| Field | Required | Rules |
|---|---|---|
| name | yes | 2–60 characters, unique ignoring case |
| slug | no | Derived from name; lowercase letters, digits, single hyphens, 2–64 |
| summary | yes | 20–200 characters |
| description | yes | Plain text, 20–4000 characters |
| category | yes | Category id from /categories |
| tags | no | Up to 8, normalized to lowercase |
| inviteUrl | yes | https URL, max 500 characters |
| supportUrl,docsUrl,repoUrl,logoUrl | no | https URLs |
New submissions start as pending. The response is 201 with the stored listing and a private reviewNote field.
/api/v1/submissions/mineAll submissions for the authenticated account, newest first, including review notes.
/api/v1/submissions/{id}Full replacement of the editable fields (same rules as create). The listing returns to pending and disappears from the public directory until it is approved again. Suspended listings cannot be edited (409).
/api/v1/submissions/{id}Withdraw a draft or rejected submission. Approved, pending or suspended listings cannot be withdrawn (409) — ask a moderator instead.
Applications & keys
These endpoints use the browser session only. They are for the dashboard, not for machine clients: a key cannot create more keys.
/api/v1/appsList your applications, or create one with name (3–80) and purpose (20–1000). Purpose is visible to moderators, not to the public.
/api/v1/apps/{id}/keysList keys (masked) or create one with an optional name. The creation response is the only time the full secret is returned:
{ "data": { "id": "…", "name": "Production", "keyPrefix": "1a2b3c4d", "lastFour": "Wxyz", "maskedKey": "mb_live_1a2b3c4d_••••Wxyz", "scopes": ["submissions:write"], "status": "active", "secret": "mb_live_1a2b3c4d_…" } }Creating a key fails with 503 key_generation_unavailable if the server's pepper is not configured. There is no way to retrieve an existing secret.
/api/v1/keys/{id}Revoke a key you own. Idempotent: revoking an already revoked key returns it unchanged.
/api/v1/apps/{id}/usageDaily totals per authenticated endpoint for all keys of the application. Rows are { key_id, day, endpoint, count }; meta.total is the sum. Public calls are not counted.
Moderation
Requires the moderator or admin role, checked on the server for every request. Browser session only.
/api/v1/moderation/submissions?status=pendingQueue for one status: draft, pending, approved, rejected or suspended.
/api/v1/moderation/submissions/{id}/decision| Field | Notes |
|---|---|
| decision | approve | reject | suspend |
| reason | Required for reject/suspend (≥ 5 characters), shown to the owner |
| featured | Admin only |
| hiddenGem | Admin only |
| verificationStatus | Admin only: verified | unverified |
Each decision writes an audit event with actor, previous status and reason.
/api/v1/moderation/reports?status=openReport queue, joined with the listing's name, slug and status.
/api/v1/moderation/reports/{id}/resolveBody: { "status": "resolved" | "dismissed", "note": "…" }. Resolution is recorded with your account and timestamp.
Rate limits
Limits are enforced by Cloudflare rate-limit bindings. They are per location and eventually accurate, not a global quota, and they are configured in wrangler.jsonc.
| Area | Limit | Keyed by |
|---|---|---|
| Public directory reads | 120 / 60 s | client IP |
| Authenticated API calls | 120 / 60 s | API key id |
| Writes (submissions, reports, keys) | 30 / 60 s | account or key id |
| Auth routes | 20 / 60 s | client IP |
| Legacy leads | 5 / 60 s | client IP |
| Legacy ratings | 20 / 60 s | client IP |
Exceeding a limit returns 429 with Retry-After: 60. Request bodies are capped at 8–16 KiB depending on the route.
Versioning & support
Everything documented here lives under /api/v1. Additive changes (new fields, new optional parameters) do not bump the version. Breaking changes get a new prefix and an overlap period; endpoints are not silently repurposed.
There is no SLA and no uptime guarantee. The API is a small self-hosted service; if it is down, the website says so instead of pretending. Security reports and API questions go to Meros.Basement@gmx.net.
Using the API means accepting the API terms and the privacy notice.
Build something with the directory
Create an application, generate a key for your tooling, and keep public discovery keyless.