Base
Base URL and method
monocrawl.com/v1
Base URL
GET
Method
x-api-key
Auth
JSON
Encoding
Data endpoints are GET with query-string parameters. The batch transport is POST /v1/batch. Responses are JSON by default. search/all and search/creators can instead stream SSE with stream=1; see streaming.
Paths
How a path resolves
The first segment after /v1 is the platform; everything after it is the endpoint id. Some endpoints take their identifier in the path rather than the query string, and the router recognises both call styles.
| Form | Example | Resolves as |
|---|---|---|
/v1/{platform}/{endpoint} | /v1/github/profile | The ordinary form. Parameters go in the query string. |
/v1/{platform}/{a}/{b} | /v1/github/repo/issues | Endpoint ids may span several segments; everything after the platform is the endpoint id. |
/v1/{platform} | /v1/status | A bare platform resolves to that platform’s index endpoint. |
/v1/{platform}/{collection}/{id} | /v1/web/jobs/job_9f3a | A trailing identifier resolves to the collection’s get endpoint, with the id passed as its declared parameter. |
/v1/{platform}/{id}/{sub} | /v1/monitors/mon_1b2c/runs | An identifier in the middle is lifted out and the surrounding words resolve the endpoint. |
A path segment is treated as an identifier rather than a route word when it contains a digit or an underscore, or runs past 24 characters — so job_9f3a and 12345 are ids while runs and checks are route words. An unknown path answers 404 ENDPOINT_NOT_FOUND.
Envelope
One success shape, one error shape
Both shapes always carry credits_used and request_id, for standard routed JSON responses. Error responses include credits_remaining only when the balance is known. Batch has a separate wrapper and per-item envelopes; streamed searches carry an envelope in their final result event.
Success · 200
{
"success": true,
"platform": "github",
"endpoint": "/v1/github/profile",
"data": { … },
"credits_used": 1,
"credits_remaining": 99,
"request_id": "req_4f2b8c1d09ae37b562",
"cached": false
}Error · any non-2xx
{
"success": false,
"error": {
"type": "RESOURCE_NOT_FOUND",
"message": "GitHub returned 404 for this resource.",
"status": 404,
"doc_url": "https://www.monocrawl.com/docs/errors#resource-not-found"
},
"credits_used": 0,
"credits_remaining": 99,
"request_id": "req_71ac0e5b39d8f2c604"
}data
The endpoint-specific payload. Many lists use { items, count, cursor }; bundles and service operations can have named collections or other structures. Consult the endpoint description.
synthetic / degraded
Sandbox-only compatibility flags. Production /v1 returns live/cache data or an error and never substitutes representative output after upstream failure.
_warnings
Optional explanatory notes inside data. Unavailable fields can be null or absent; a warning is not guaranteed for every missing field.
error.doc_url points at the exact section of the errors page for that error type — a client can surface it verbatim.
Pagination
Cursors, not page maths
Many list endpoints return items, count and cursor inside data. Where supported, pass the cursor back with the original filters. A null cursor means no usable continuation was supplied, not proof that all history was retrieved. Cursors are opaque — some are page numbers, some are upstream tokens — so echo the value you were given rather than deriving the next one.
$ curl "https://www.monocrawl.com/v1/github/profile/repos?handle=torvalds&limit=50" \
-H "x-api-key: mn_your_key_here"
"data": {
"items": [ … 50 rows … ],
"count": 50,
"cursor": "2"
}
# continue with the cursor exactly as it was given to you
$ curl ".../v1/github/profile/repos?handle=torvalds&limit=50&cursor=2" -H "x-api-key: …"Page-size parameters, defaults and caps vary by endpoint. Some routes use pageor do not support continuation. Each page is a separate request under that endpoint’s billing rules; a larger requested limit is not a guarantee of more results or a lower price.
Batch
POST /v1/batch — up to 20 calls at once
The batch endpoint takes a list of ordinary requests and runs each one through the full pipeline independently. Items may target different platforms. The wrapper itself costs nothing — you pay only for the calls inside it.
curl -X POST "https://www.monocrawl.com/v1/batch" \
-H "x-api-key: mn_your_key_here" \
-H "content-type: application/json" \
-d '{
"requests": [
{ "platform": "github", "endpoint": "profile", "params": { "handle": "torvalds" } },
{ "platform": "bluesky", "endpoint": "profile", "params": { "handle": "bsky.app" } }
]
}'{
"success": true,
"count": 2,
"succeeded": 2,
"failed": 0,
"credits_used": 2,
"credits_remaining": 97,
"results": [
{ "index": 0, "status": 200, "success": true, "platform": "github",
"endpoint": "/v1/github/profile", "data": { … }, "credits_used": 1,
"credits_remaining": 98, "request_id": "req_…", "cached": false },
{ "index": 1, "status": 200, "success": true, "platform": "bluesky",
"endpoint": "/v1/bluesky/profile", "data": { … }, "credits_used": 1,
"credits_remaining": 97, "request_id": "req_…", "cached": false }
]
}Twenty items maximum
A larger list is rejected with 400 INVALID_PARAMETERS and details.max, before anything is charged. Split bigger jobs client-side.
Items fail independently
An upstream failure or an out-of-credits item degrades that item only. Its entry carries its own status and error; its siblings still return data.
Header auth only
Batch reads only x-api-key, not Authorization: Bearer. Query-string API keys are not supported on batch or GET routes.
Check each item
The wrapper’s success is true whenever the batch itself was accepted. Read succeeded, failed, and each item’s own status and success.
The top-level credits_used is the sum across items; credits_remaining, when present, is the lowest balance any item observed while the batch ran. For an exact figure afterwards, call /v1/credits/balance. Batch items are admitted individually, so each one counts against the per-key rate limit. The wrapper does not return per-item HTTP headers or a top-level request_id. Idempotency-Key and Cache-Control headers are not forwarded to items.
Discovery
Read the catalogue at runtime
These endpoints describe the API to a client without any hard-coded list. They all cost 0 credits, so a service can refresh its view of the catalogue on every boot.
| Endpoint | Parameters | Returns |
|---|---|---|
GET /v1/utility/endpoints | platform, search, method | Every active endpoint with its id, name, credit_cost, params and description. The authoritative catalogue. |
GET /v1/utility/endpoint | id or url | One endpoint in full, including a ready-to-run curl with its required parameters filled from examples. |
GET /v1/utility/quickstart | platform | A low-cost registered data endpoint (for a platform, or overall) with its curl and the envelope it will return. |
GET /v1/utility/llms | platform, format | The whole catalogue as one payload for an agent: format=markdown (default) or format=json. |
GET /v1/credits/balance | — | credits_remaining, credits_lifetime and when the balance last changed. |
GET /v1/credits/transactions | limit, cursor, request_id | Your credit ledger, newest first — every debit and credit with the request_id that caused it. |
GET /v1/status | — | Per-platform requests, error_rate, p50/p95 latency and state over the last 15 minutes. |
utility/llms is the fastest way to teach an agent the whole surface: one call returns the base URL, the auth header, both envelope shapes, the error type list and every endpoint with its cost and parameters.
For humans, the browsable endpoint reference lists every operation with its parameters and a ready-to-run curl. For tooling, /openapi.json (or /openapi.yaml) is the same registry as an OpenAPI 3.1 document — import it into Postman, Insomnia or a client generator. These surfaces share the registry but expose different subsets and metadata; registration and recorded proof are not a guarantee of current availability.
Headers
What comes back on the wire
| Header | When | Meaning |
|---|---|---|
retry-after | 429 or 503, when provided | Seconds to wait before retrying. Per-key rate-limit refusals also include error.details.retry_after_seconds; other refusals may omit that field. |
content-type | when supplied | application/json by default; streamed searches use text/event-stream. |
x-request-id | when supplied | Same value as request_id in the body. Quote it when contacting support. |
x-credits-used | when supplied | Credits charged for this request — 0 on any failure. |
x-credits-remaining | when supplied | Your balance after the request. |
x-cache | when supplied | HIT or MISS on successful routed responses. A cache hit costs 0 credits — see endpoint pricing. |
x-idempotent-replay | replayed requests | true when the response came from an earlier identical call rather than a new one. See below. |
x-ratelimit-limit · x-ratelimit-remaining · x-ratelimit-reset | routed, rate-admitted JSON responses | The request-window cap, what is left of it in the current sliding window (this request included) and the Unix second at which its oldest entry ages out. See rate limits. |
x-concurrency-limit · x-concurrency-remaining | routed, rate-admitted JSON responses | The in-flight cap and the slots still free as this request entered. Not an alias of the request-window headers: a client that conflates the two throttles against the wrong ceiling. |
deprecation · sunset · link | deprecated endpoints only | The endpoint is scheduled for removal: when that was announced, when it happens, and where to read about it. Formats and the notice period are on versioning. |
Early authentication refusals, batch wrappers, SSE and idempotent replays may omit some headers. Rate and concurrency headroom are not duplicated in the body. Read credits_remaining when present, or call /v1/credits/balance for free.
Idempotency
Retry a lost request without paying twice
If a response is lost — a timeout, a dropped connection, a crash between the call and reading it — you cannot tell whether the request landed. Retrying blindly risks paying twice for one result. Send an Idempotency-Key header on the first direct GET request and reuse it on retries. A stored replay adds no charge. This header does not protect batch items or MCP calls. If the idempotency store is unavailable, the call proceeds without replay protection and returnsx-idempotency-status: unavailable; do not assume an automatic retry is deduplicated.
# first call: runs, and is charged
$ curl "https://www.monocrawl.com/v1/github/profile?handle=torvalds" \
-H "x-api-key: mn_your_key_here" \
-H "Idempotency-Key: 8f3a1c2e-order-4471"
x-credits-used: 1
{ "success": true, "data": { … }, "credits_used": 1, "request_id": "req_4f2b8c…" }
# the response was lost — retry with the SAME key
$ curl "https://www.monocrawl.com/v1/github/profile?handle=torvalds" \
-H "x-api-key: mn_your_key_here" \
-H "Idempotency-Key: 8f3a1c2e-order-4471"
x-idempotent-replay: true
{ "success": true, "data": { … }, "credits_used": 1, "request_id": "req_4f2b8c…" }
# ^ the stored original result. No new charge; its balance is historical.The replay preserves the result
You get the stored original HTTP status and JSON body, with x-idempotent-replay: true. JSON formatting and the original response headers are not preserved. No new upstream call is made and no usage event is recorded.
It costs nothing
The original charge stands; the replay adds zero. The body still shows what the first call cost, because it IS the first call’s response.
Keys are yours alone
Scoped to your account, so your keys can never collide with another customer’s. Any string up to 255 characters — a UUID per logical operation works well.
Records last 24 hours
After that the key is free to reuse. Reusing one sooner for a DIFFERENT request returns 422 rather than the wrong body.
If a second request arrives while the first is still in flight, the second gets 409 with details.reason = idempotency_in_progress — wait for the first rather than racing it. The header is entirely optional; without it nothing about a request changes.
Webhooks
Signed events pushed to your server
Register an https URL in the console (Dashboard → API → Integrations) and Monocrawl POSTs you a signed JSON event when something happens on your account — an async job finishing, a failed job and its refund status, a low balance or an auto-recharge outcome. Up to five endpoints per account, each with its own signing secret and its own event filter.
POST https://example.com/monocrawl/webhook
content-type: application/json
monocrawl-event: job.completed
monocrawl-delivery: whd_8c1d09ae37b562
monocrawl-signature: t=1767225600,v1=5f8a…e2c1
{
"id": "whd_8c1d09ae37b562",
"type": "job.completed",
"created_at": "2026-01-01T00:00:00.000Z",
"data": { "job_id": "job_9f3a", "kind": "crawl", "credits_charged": 40 }
}Verify the signature
Every delivery carries monocrawl-signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 over `${t}.${rawBody}` with your endpoint's secret. Verify before trusting anything in the body — anyone can POST to a public URL, a valid signature proves possession of the signing secret. Keep that secret private.
import crypto from 'node:crypto';
// rawBody is the original Buffer (or unmodified UTF-8 string), BEFORE JSON parsing.
export function verifyMonocrawlSignature(rawBody, header, secret, nowMs = Date.now()) {
const match = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? '');
if (!match || !secret) return false;
const [, timestamp, signature] = match;
const seconds = Number(timestamp);
if (!Number.isSafeInteger(seconds) || Math.abs(nowMs / 1000 - seconds) > 300) return false;
const expected = crypto.createHmac('sha256', secret)
.update(timestamp + '.').update(rawBody).digest();
const received = Buffer.from(signature, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(expected, received);
}Event types
| Event | Fires when |
|---|---|
billing.low_balance | Balance crossed the configured alert threshold; by default, one fifth of the last purchased pack. |
billing.credits_exhausted | Balance reached zero. Paid requests requiring more credits return 402; free endpoints remain usable. Emitted once per exhaustion episode. |
billing.purchase_succeeded | A pack purchase settled and its credits were granted. |
billing.auto_recharge.succeeded | An enabled auto-recharge charged the saved card and granted credits. |
billing.auto_recharge.failed | Auto-recharge could not charge the card; check billing or top up manually. |
subscription.renewed | Legacy subscriptions only: a period renewed. Current offers are one-time packs, not subscriptions. |
subscription.ended | Legacy subscriptions only: the subscription ended and its remaining plan credits expired. |
job.completed | An async job finished. Use data.job_id to retrieve its result. |
job.failed | An async job failed. Inspect credits_refunded and refund_status: a refund marked due is pending, not yet credited. |
monitor.findings | A monitor recorded findings for delivery to its configured destinations. |
monitor.attention | A monitor needs attention, for example an ambiguous subject or repeated failed checks. |
monitor.digest | A scheduled monitor summary is ready for delivery. |
test.ping | A test delivery requested from the dashboard. |
Answer 2xx fast
Anything else — including a timeout past 10 seconds — counts as a failed attempt. Acknowledge first, process after; redirects are never followed.
Five delayed retries
After successive failures the delays are 1 min, 5 min, 30 min, 2 h and 12 h: about 14 h 36 min in total, plus processing and worker scheduling time. After the schedule is exhausted the delivery is marked failed and stays in your history.
Dedupe on the delivery id
Delivery is at-least-once: if your 2xx is lost on the wire, the same id can arrive again. Treat the id as the idempotency key on your side.
Dead endpoints turn themselves off
Ten consecutive failed deliveries auto-disable the endpoint (the reason is shown in the console). Fix the receiver, then re-enable — that resets the count.
Rotating a secret takes effect immediately — deliveries signed with the old secret stop verifying at that moment, so rotate when your receiver is ready for the new one. The "Test" button in the console sends a test.ping synchronously and shows you exactly what your endpoint answered.