API Reference
The webref REST API. Same API that powers the web app and the CLI. The MCP endpoint is separate at /mcp.
Base URL: https://webref.ai/api
Authentication
Include your API key in the Authorization header:
Authorization: Bearer wbrf_your_key_hereGet keys from your dashboard.
POST /api/research
Research the web. Questions can include URLs for the planner to read or compare.
Request:
curl -X POST https://webref.ai/api/research \
-H "Authorization: Bearer wbrf_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: research-20260821-001" \
-d '{"query": "how to use React hooks"}'API-key requests require an Idempotency-Key. Use a unique value for each logical request and reuse it for retries of that same request. Reusing a key with a different query returns 409 IDEMPOTENCY_CONFLICT.
Body:
| Field | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Question, URL(s), or both (max 1000 characters) |
Response (202 Accepted):
{
"receiptId": "rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV",
"privateUrl": "https://webref.ai/r/rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV",
"statusUrl": "https://webref.ai/api/research/progress/rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV",
"state": "queued"
}Research is durable after admission. Poll statusUrl until the state is completed or failed. Repeating the original request with the same idempotency key returns the same job instead of reserving more credits.
privateUrl belongs to the account that created the research. Do not cite or share it. A published receipt also includes shareUrl.
Add ?wait=true to wait up to 4 minutes 50 seconds for a normal completed response. If the run is still active, the endpoint returns the same 202 Accepted response and the client continues through statusUrl.
Research costs 1–3 credits based on the rounds used. The service reserves the account's maximum depth at admission, settles the actual number used on completion, and refunds failed work.
GET /api/research/progress/{receiptId}
Get the current state and bounded progress for an admitted research job. The request must authenticate as the receipt owner. Other accounts and unauthenticated requests receive 404.
Request:
curl https://webref.ai/api/research/progress/rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV \
-H "Authorization: Bearer wbrf_your_key"Running response (200 OK):
{
"status": "running",
"state": "running",
"stage": "reading",
"queries": [
{ "query": "React hooks official guide" }
],
"sources": [
{
"title": "React Reference",
"url": "https://react.dev/reference/react",
"status": "read"
}
]
}The state is one of queued, running, completed, or failed. Progress snapshots are bounded and may advance between polls.
Completed response (200 OK):
{
"status": "completed",
"state": "completed",
"stage": "completed",
"content": "# React Hooks\n\nHooks let you use state...",
"trust": {},
"sources": ["https://react.dev/reference/react"],
"creditsUsed": 2,
"duration": "41.27s",
"receiptId": "rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV",
"privateUrl": "https://webref.ai/r/rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV"
}An owner request for failed research has state: "failed" and a structured failure object. An unknown receipt or a receipt owned by another account returns 404 NOT_FOUND.
PUT /api/receipts/{receiptId}/publication
Publish a receipt as a read-only live document. Owner session or API key required. Publishing twice returns the same active link.
Request:
curl -X PUT https://webref.ai/api/receipts/rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV/publication \
-H "Authorization: Bearer wbrf_your_key"Response (200 OK):
{
"visibility": "published",
"shareUrl": "https://webref.ai/s/shr_t7hKRdp1YLc_nKWL7hvxYhB-jP4njKiO"
}The shared link follows the receipt's current state and later follow-ups. It grants read access only.
DELETE /api/receipts/{receiptId}/publication
Revoke the active shared link. Owner session or API key required. Repeating the request is safe.
Response (200 OK):
{
"visibility": "private"
}Publishing again creates a fresh shareUrl; the revoked link stays unavailable.
GET /api/me
Get current user info.
Request:
curl https://webref.ai/api/me \
-H "Authorization: Bearer wbrf_your_key"Response (200):
{
"id": 1,
"email": "[email protected]",
"name": "Your Name",
"emailVerified": true,
"credits": 1034,
"legacyCredits": 0,
"subscriptionPlan": "pro",
"subscriptionPendingPlan": "",
"subscriptionStatus": "active",
"subscriptionBillingPeriod": "monthly",
"subscriptionAllowance": 700,
"extraUsageCredits": 334,
"researchAllowed": true,
"hasActiveSubscription": true,
"role": "user",
"createdAt": "2026-03-11T12:34:56Z"
}GET /api/usage
Get dashboard usage totals, 30-day aggregate activity, onboarding state, and any retained detailed history rows.
Request:
curl https://webref.ai/api/usage \
-H "Authorization: Bearer wbrf_your_key"Response (200):
{
"totalSearches": 42,
"searchesThisMonth": 9,
"dailyActivity": [
{
"date": "2026-03-25",
"researchCount": 1,
"webSearchCount": 0,
"readCount": 0
},
{
"date": "2026-03-26",
"researchCount": 2,
"webSearchCount": 1,
"readCount": 1
}
],
"searchHistory": [
{
"id": 123,
"type": "search",
"query": "how does HTTP caching work",
"caller": "CLI: Personal Key",
"creditsUsed": 2,
"createdAt": "2026-03-26T12:00:00Z",
"hasSavedResult": true
}
]
}dailyActivity contains aggregate counts. searchHistory contains research rows, and hasSavedResult tells the dashboard whether a persisted Markdown answer is available for preview.
GET /api/usage/history/{id}/result
Get the saved markdown answer plus the slim trace slices the dashboard renders for a history row: the final source URLs and the planned sub-queries from the planning step. Full pipeline traces are not stored in the database — they are shipped to Axiom for debugging.
Request:
curl https://webref.ai/api/usage/history/123/result \
-H "Authorization: Bearer wbrf_your_key"Response (200):
{
"content": "## Browser caching\n\nA cache reuses a stored response...",
"sourceUrls": ["https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching"],
"plannedQueries": ["HTTP caching browser cache control"]
}Returns 404 NOT_FOUND when the history row does not exist, is not a research row, does not belong to the authenticated user, or has no persisted saved result.
GET /api/credits
Check credit balance.
Request:
curl https://webref.ai/api/credits \
-H "Authorization: Bearer wbrf_your_key"Response (200):
{
"balance": 1234,
"subscriptionAllowance": 700,
"extraUsageCredits": 334,
"subscriptionPlan": "pro",
"subscriptionStatus": "active"
}POST /api/billing/purchase
Create a Stripe Checkout Session for a self-serve subscription or prepaid extra usage. Session or API key auth required.
Request:
curl -X POST https://webref.ai/api/billing/purchase \
-H "Authorization: Bearer wbrf_your_key" \
-H "Content-Type: application/json" \
-d '{"plan": "pro", "billingPeriod": "monthly"}'For extra usage:
curl -X POST https://webref.ai/api/billing/purchase \
-H "Authorization: Bearer wbrf_your_key" \
-H "Content-Type: application/json" \
-d '{"extraCredits": 1000}'Response (200):
{
"checkoutUrl": "https://checkout.stripe.com/c/pay/...",
"checkoutId": "cs_live_..."
}The checkout success URL returns to /dashboard/billing?success=true&checkout_id={CHECKOUT_SESSION_ID}. Stripe replaces the template with the completed Checkout Session ID. The dashboard polls until extra-usage purchases show as completed in purchase history, or until subscription credits reflect an active paid plan.
GET /api/billing/history
List recent extra-usage purchases for the current user. Session or API key auth required.
Response (200):
{
"purchases": [
{
"id": 1,
"checkoutId": "cs_live_...",
"tier": "extra_usage",
"amountCents": 7500,
"credits": 1000,
"status": "completed",
"createdAt": "2026-05-12T12:00:00Z"
}
]
}POST /api/billing/portal
Create a Stripe customer portal session for payment methods, receipts, invoices, cancellation, and payment recovery. Session or API key auth required.
Response (200):
{
"url": "https://billing.stripe.com/p/session/..."
}GET /api/keys
List API keys (metadata only).
Request:
curl https://webref.ai/api/keys \
-H "Authorization: Bearer wbrf_your_key"Response (200):
{
"keys": [
{
"id": 42,
"name": "Production",
"keyPrefix": "wbrf_4f8k2m9",
"lastUsedAt": "2026-08-21T14:22:00Z",
"createdAt": "2026-08-20T10:30:00Z",
"isDefault": false,
"isLegacy": false
}
]
}POST /api/keys
Create a new API key.
Request:
curl -X POST https://webref.ai/api/keys \
-H "Authorization: Bearer wbrf_your_key" \
-H "Content-Type: application/json" \
-d '{"name": "CI/CD Pipeline"}'Response (201):
{
"id": 43,
"name": "CI/CD Pipeline",
"key": "wbrf_abc123xyz789...",
"keyPrefix": "wbrf_abc123x",
"createdAt": "2026-08-21T09:00:00Z"
}The response includes the full key; store it securely. Modern encrypted keys
can also be revealed later from the dashboard. Legacy hashed-only keys cannot
be revealed, which is what isLegacy reports in the list response.
DELETE /api/keys/{id}
Revoke an API key.
Request:
curl -X DELETE https://webref.ai/api/keys/43 \
-H "Authorization: Bearer wbrf_your_key"Response (204 No Content): no body.
Error responses
All errors follow this format:
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits. Open billing to buy extra usage or upgrade your plan."
}
}| Status | Code | When |
|---|---|---|
| 400 | BAD_REQUEST |
Missing/malformed request |
| 401 | UNAUTHORIZED |
Invalid API key |
| 402 | INSUFFICIENT_CREDITS |
Balance is zero |
| 404 | NOT_FOUND |
Resource doesn't exist |
| 409 | IDEMPOTENCY_CONFLICT |
Idempotency key was reused with different research |
| 422 | VALIDATION_FAILED |
Validation failed |
| 429 | OUTSTANDING_LIMIT |
Account already has its allowed number of active jobs |
| 500 | INTERNAL_ERROR |
Server error |
| 503 | BACKLOG_FULL or ADMISSIONS_PAUSED |
Research cannot accept work yet |
Client examples
See the HTTP API integration guide for complete Python and JavaScript polling examples plus a Go request example.