Documentation

existAPI v1

The same moderated directory the website uses, exposed as a small JSON API. Public reads need no key; authenticated writes use an application key bound to your account.

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/mine

Errors

Every v1 error uses one envelope:

{ "error": { "code": "validation_error", "message": "Summary must be 20–200 characters." } }
StatusMeaning
400Invalid input or unknown filter
401Missing, invalid or revoked credentials
403Authenticated but not allowed (role, scope or suspended account)
404Not found, or not visible to you
409Conflict: duplicate name/slug, or a state that cannot change
413 / 415Body too large, or not application/json
429Rate limited — respect Retry-After
503Database 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

GET/api/v1/discover

Paginated search over approved listings.

QueryTypeNotes
qstringName/summary substring, max 80 characters
categorystringOne of the category ids below
tagstringSingle tag, lowercase, max 24 characters
gemtrueOnly editorial hidden gems
sortenumnew (default) | name | oldest
pageinteger≥ 1, default 1
perPageinteger1–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 } }
GET/api/v1/featured

Up to 12 approved listings where moderators set featured. Not paid placement.

GET/api/v1/categories

Category ids, labels, descriptions and sort order. Use these ids for filters.

{ "data": [ { "id": "utility", "label": "Utility", "description": "…", "sort_order": 10 } ], "meta": { "perPage": 9 } }
GET/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.

POST/api/v1/bots/{id}/reports

Report an approved listing. No account is required. Rate limited by IP.

FieldTypeNotes
reasonenumspam | malicious | impersonation | broken | other
detailsstringOptional, 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.

POST/api/v1/submissions
FieldRequiredRules
nameyes2–60 characters, unique ignoring case
slugnoDerived from name; lowercase letters, digits, single hyphens, 2–64
summaryyes20–200 characters
descriptionyesPlain text, 20–4000 characters
categoryyesCategory id from /categories
tagsnoUp to 8, normalized to lowercase
inviteUrlyeshttps URL, max 500 characters
supportUrl,docsUrl,repoUrl,logoUrlnohttps URLs

New submissions start as pending. The response is 201 with the stored listing and a private reviewNote field.

GET/api/v1/submissions/mine

All submissions for the authenticated account, newest first, including review notes.

PATCH/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).

DELETE/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.

GET / POST/api/v1/apps

List your applications, or create one with name (3–80) and purpose (20–1000). Purpose is visible to moderators, not to the public.

GET / POST/api/v1/apps/{id}/keys

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

DELETE/api/v1/keys/{id}

Revoke a key you own. Idempotent: revoking an already revoked key returns it unchanged.

GET/api/v1/apps/{id}/usage

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

GET/api/v1/moderation/submissions?status=pending

Queue for one status: draft, pending, approved, rejected or suspended.

POST/api/v1/moderation/submissions/{id}/decision
FieldNotes
decisionapprove | reject | suspend
reasonRequired for reject/suspend (≥ 5 characters), shown to the owner
featuredAdmin only
hiddenGemAdmin only
verificationStatusAdmin only: verified | unverified

Each decision writes an audit event with actor, previous status and reason.

GET/api/v1/moderation/reports?status=open

Report queue, joined with the listing's name, slug and status.

POST/api/v1/moderation/reports/{id}/resolve

Body: { "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.

AreaLimitKeyed by
Public directory reads120 / 60 sclient IP
Authenticated API calls120 / 60 sAPI key id
Writes (submissions, reports, keys)30 / 60 saccount or key id
Auth routes20 / 60 sclient IP
Legacy leads5 / 60 sclient IP
Legacy ratings20 / 60 sclient 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.

Work in progress — we’re shipping an update. The site stays online, but some areas may change.Changelog