webref/docs

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_here

Get keys from your dashboard.


POST /api/research

Research the web. Questions can include URLs for the planner to read or compare.

Request:

bash
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):

json
{
  "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:

bash
curl https://webref.ai/api/research/progress/rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV \
  -H "Authorization: Bearer wbrf_your_key"

Running response (200 OK):

json
{
  "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):

json
{
  "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:

bash
curl -X PUT https://webref.ai/api/receipts/rec_CiBzgzrTfNNWQjBrNgQ8LiW_MIOhKXfV/publication \
  -H "Authorization: Bearer wbrf_your_key"

Response (200 OK):

json
{
  "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):

json
{
  "visibility": "private"
}

Publishing again creates a fresh shareUrl; the revoked link stays unavailable.


GET /api/me

Get current user info.

Request:

bash
curl https://webref.ai/api/me \
  -H "Authorization: Bearer wbrf_your_key"

Response (200):

json
{
  "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:

bash
curl https://webref.ai/api/usage \
  -H "Authorization: Bearer wbrf_your_key"

Response (200):

json
{
  "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:

bash
curl https://webref.ai/api/usage/history/123/result \
  -H "Authorization: Bearer wbrf_your_key"

Response (200):

json
{
  "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:

bash
curl https://webref.ai/api/credits \
  -H "Authorization: Bearer wbrf_your_key"

Response (200):

json
{
  "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:

bash
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:

bash
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):

json
{
  "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):

json
{
  "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):

json
{
  "url": "https://billing.stripe.com/p/session/..."
}

GET /api/keys

List API keys (metadata only).

Request:

bash
curl https://webref.ai/api/keys \
  -H "Authorization: Bearer wbrf_your_key"

Response (200):

json
{
  "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:

bash
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):

json
{
  "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:

bash
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:

json
{
  "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.