Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: hyperresearch Cloud Usage API
version: 1.0.0
description: 'REST v1 for hosted hyperresearch runs, vault access, verification, and billing. Times are RFC 3339 UTC; ids are prefixed ULIDs.
**Authentication.** Send an API key as `Authorization: Bearer hr_live_…`. Each operation lists the scope it needs (`x-hr-scope`, and in its `security` requirement). OAuth 2.1 (authorization code + PKCE, `oauth2` scheme) issues tokens for the hosted MCP server at https://mcp.hyperresearch.ai/mcp; REST calls use an API key.
**Errors.** Every 4xx/5xx body is the `ErrorEnvelope`: `error.type`, a stable `error.code`, a human `message`, the offending `param` when there is one, and the `request_id` to quote to support. Branch on `code`, never on `message`.
**Rate limits.** Responses carry `RateLimit-Policy` and `RateLimit` (draft-ietf-httpapi-ratelimit-headers) alongside the older `X-RateLimit-*` headers. A 429 always carries `Retry-After` (seconds) and `error.retry_after`.
**Versioning.** The version is in the path. `/v1` is stable: within it, changes are additive only (new operations, new optional request fields, new response fields, new enum values a client must tolerate). A breaking change ships only under a new path version. A deprecated operation is marked `deprecated: true` here and answers with `Deprecation` (RFC 9745) and `Sunset` (RFC 8594) headers and `Link: rel="deprecation"` at least six months before it is removed, and is listed under Deprecations on the policy page: https://hyperresearch.ai/docs/versioning'
contact:
name: hyperresearch
url: https://hyperresearch.ai
x-api-lifecycle:
version_scheme: path
current: v1
status: stable
breaking_changes: new path version only
deprecation_notice_min_days: 182
deprecation_signals:
- 'openapi deprecated: true'
- Deprecation header (RFC 9745)
- Sunset header (RFC 8594)
- Link rel="deprecation"
- policy page deprecations list
policy: https://hyperresearch.ai/docs/versioning
servers:
- url: https://api.hyperresearch.ai
description: production
tags:
- name: Usage
description: Usage and billing
paths:
/v1/usage:
get:
tags:
- Usage
summary: Usage for this workspace
description: Runs by tier and metered usage by kind for the period, plus the account plan, spend cap and month-to-date spend. Reads D1 only — never Stripe. `charged` is the runs that were billed, tier by tier, and `totals.charged_cents` is exactly their sum; `included` lists the trial-covered runs apart, at $0, each flagged `included_in_trial`. Each `metered` kind separates its `charges` from its `refunds`, so a net quantity is never shown beside a gross event count.
operationId: getUsage
x-hr-scope: admin
x-hr-auth: key-or-session
security:
- bearerAuth:
- admin
- sessionCookie:
- admin
parameters:
- schema:
type: string
example: '2026-09-01T00:00:00Z'
required: false
name: from
in: query
- schema:
type: string
example: '2026-10-01T00:00:00Z'
required: false
name: to
in: query
responses:
'200':
description: Usage
headers:
RateLimit-Policy:
$ref: '#/components/headers/RateLimit-Policy'
RateLimit:
$ref: '#/components/headers/RateLimit'
X-RateLimit-Limit:
$ref: '#/components/headers/X-RateLimit-Limit'
X-RateLimit-Remaining:
$ref: '#/components/headers/X-RateLimit-Remaining'
X-RateLimit-Reset:
$ref: '#/components/headers/X-RateLimit-Reset'
X-RateLimit-Bucket:
$ref: '#/components/headers/X-RateLimit-Bucket'
content:
application/json:
schema:
$ref: '#/components/schemas/UsageSummary'
'400':
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'403':
description: Insufficient scope or plan
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'404':
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'429':
description: Rate limited or over quota
headers:
Retry-After:
$ref: '#/components/headers/Retry-After'
RateLimit-Policy:
$ref: '#/components/headers/RateLimit-Policy'
RateLimit:
$ref: '#/components/headers/RateLimit'
X-RateLimit-Limit:
$ref: '#/components/headers/X-RateLimit-Limit'
X-RateLimit-Remaining:
$ref: '#/components/headers/X-RateLimit-Remaining'
X-RateLimit-Reset:
$ref: '#/components/headers/X-RateLimit-Reset'
X-RateLimit-Bucket:
$ref: '#/components/headers/X-RateLimit-Bucket'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'500':
description: Internal error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
components:
schemas:
ErrorEnvelope:
type: object
properties:
error:
type: object
properties:
type:
type: string
enum:
- invalid_request
- authentication
- permission
- not_found
- rate_limited
- quota_exceeded
- budget_blocked
- conflict
- provider_unavailable
- internal
example: invalid_request
code:
type: string
enum:
- missing_api_key
- malformed_api_key
- invalid_api_key
- api_key_expired
- api_key_revoked
- account_suspended
- workspace_unavailable
- session_expired
- insufficient_scope
- signup_closed
- role_not_permitted
- membership_required
- csrf_required
- csrf_invalid
- origin_not_allowed
- https_required
- key_route_restricted
- tier_not_allowed
- runs_paused
- invalid_body
- invalid_query
- invalid_path
- query_too_short
- query_not_research
- query_too_long
- query_refused
- invalid_title
- invalid_brief
- invalid_must_read
- invalid_research_brief
- invalid_time_window
- invalid_pulse
- invalid_metadata
- invalid_tier
- too_many_required_headings
- sources_max_above_ceiling
- reuse_scope_needs_project
- invalid_webhook_url
- metadata_too_large
- invalid_settings
- invalid_step
- unsupported_tier
- tier_not_offered
- plan_not_offered
- provider_excluded
- dry_run_not_enabled
- invalid_dry_run_fault
- chaos_faults_not_enabled
- invalid_chaos_fault
- gone
- run_not_found
- key_not_found
- workspace_not_found
- webhook_not_found
- artifact_not_found
- note_not_found
- escalation_not_found
- source_not_found
- connection_not_found
- project_not_found
- answer_not_found
- account_not_found
- feature_not_enabled
- idempotency_key_reuse
- idempotency_in_flight
- run_not_resumable
- run_not_finish_resumable
- run_finish_resume_exhausted
- run_recovering
- rerun_not_platform
- platform_rerun_taken
- run_not_clarifying
- result_not_ready
- verification_running
- workspace_not_empty
- turnstile_invalid
- turnstile_replayed
- demo_daily_limit
- demo_receipt_invalid
- demo_disabled
- anon_session_invalid
- anon_refine_limit
- anon_refine_capacity
- refine_draft_not_found
- user_not_found
- member_not_found
- member_exists
- last_owner
- cannot_self_elevate
- workspace_limit
- invitation_not_found
- invitation_expired
- invitation_email_mismatch
- account_mismatch
- ip_not_allowed
- chat_keys_retired
- project_name_taken
- project_archived
- run_not_filable
- confirmation_mismatch
- ownership_transfer_required
- account_deleting
- export_cooldown
- migration_pending
- rate_limit_exceeded
- concurrency_limit
- spend_cap_exceeded
- trial_exhausted
- payment_method_required
- payment_disputed
- card_hold_unavailable
- card_required
- fixture_not_shareable
- too_many_url_sources
- no_citations
- insufficient_balance
- wallet_frozen
- topup_amount_invalid
- topup_daily_limit
- answers_capacity
- engine_unavailable
- temporarily_unavailable
- platform_capacity
- daily_run_limit
- internal_budget
- orchestrator_unavailable
- source_unconfigured
- source_rate_limited
- source_upstream_unavailable
- deployment_misconfigured
- not_implemented
- pdf_unavailable
- internal_error
example: query_too_short
message:
type: string
param:
type: string
retry_after:
type: integer
retryable:
type: boolean
description: 'Whether the same request may succeed if sent again unchanged (after `retry_after` when present): true for 429, 500, 502, 503, 504 and `idempotency_in_flight`; false for every other 4xx and 501.'
request_id:
type: string
example: req_01J9ZQ7V6H0000000000000000
required:
- type
- code
- message
- retryable
- request_id
required:
- error
UsageTotals:
type: object
properties:
quantity:
type: integer
amount_cents:
type: integer
events:
type: integer
required:
- quantity
- amount_cents
- events
description: The non-negative rows only — the metered events themselves, charged and included alike. `charges.quantity === events` for a per-run meter.
UsageSummary:
type: object
properties:
period:
type: object
properties:
from:
type: string
to:
type: string
required:
- from
- to
workspace_id:
type: string
runs:
type: array
items:
type: object
properties:
tier:
type: string
runs:
type: integer
description: Runs of this tier created in the period.
price_cents:
type: integer
description: Reserved list price on the control rows. 0 for a run an included (trial) allowance covered — this is NOT what was charged.
amount_cents:
type: integer
description: What the billing ledger metered for these runs, refunds included. Sums to `totals.run_amount_cents`.
metered_runs:
type: integer
description: How many of these runs have a ledger row yet. `runs - metered_runs` is the number still in flight.
included_runs:
type: integer
description: How many of these runs an included (trial) allowance covered, metered at $0.
charged_runs:
type: integer
description: '`runs - included_runs`: the runs no allowance covered.'
charged_cents:
type: integer
description: What the ledger metered for the charged runs alone.
included_cents:
type: integer
description: What the ledger metered for the included runs. $0 by definition.
complimentary_runs:
type: integer
description: How many of these runs an operator grant comped (0041). Taken OUT of `charged_runs`, never netted into it.
complimentary_cents:
type: integer
description: What the comped runs would have cost — the real price, which their ledger rows carry. Not part of `charged_cents`.
required:
- tier
- runs
- price_cents
- amount_cents
- metered_runs
- included_runs
- charged_runs
- charged_cents
- included_cents
charged:
type: array
items:
type: object
properties:
tier:
type: string
runs:
type: integer
amount_cents:
type: integer
metered_runs:
type: integer
included_in_trial:
type: boolean
enum:
- false
required:
- tier
- runs
- amount_cents
- metered_runs
- included_in_trial
description: Runs that were billed this period, by tier, with what they cost. `totals.charged_cents` is exactly the sum of these amounts — no other number has to be subtracted to arrive at it (E2E2-07).
included:
type: array
items:
type: object
properties:
tier:
type: string
runs:
type: integer
amount_cents:
type: integer
description: 'Always 0: an included run is not charged.'
included_in_trial:
type: boolean
enum:
- true
required:
- tier
- runs
- amount_cents
- included_in_trial
description: Trial-covered runs, listed apart from the charged ones so "$0.00" reads as "included" rather than as "free by accident".
complimentary:
type: array
items:
type: object
properties:
tier:
type: string
runs:
type: integer
amount_cents:
type: integer
description: The real price of these runs, which is what their ledger rows carry — a comped run is not a $0 run.
complimentary:
type: boolean
enum:
- true
required:
- tier
- runs
- amount_cents
- complimentary
description: Runs an operator grant comped (0041), listed apart from the charged ones and NOT included in `totals.charged_cents`. Absent when this account has none.
metered:
type: array
items:
type: object
properties:
kind:
type: string
quantity:
type: integer
description: 'Net of refunds, like `amount_cents` beside it. To show a quantity next to `events`, use `charges.quantity`: it counts the same population (E2E2-07).'
amount_cents:
type: integer
description: Net of refunds.
events:
type: integer
description: The metered events of this kind in the period — charged and included together, refunds excluded. ALWAYS equal to `charges.quantity` for a one-unit-per-event meter such as runs, because they are the same rows counted twice; a row that read `Research runs — 7 — $0.00 — 8 events` was counting a refund as an eighth run (E2E4A-10). Show `charges.amount_cents` beside it for what those events were charged.
gross_events:
type: integer
description: Every ledger row of this kind, refunds included. `events + refunds.events`.
charges:
$ref: '#/components/schemas/UsageTotals'
refunds:
allOf:
- $ref: '#/components/schemas/UsageTotals'
- description: The negative rows only; `quantity` and `amount_cents` stay negative. A refund reverses an event, it is not an event, so it is never counted in `events`.
required:
- kind
- quantity
- amount_cents
- events
- gross_events
- charges
- refunds
totals:
type: object
properties:
amount_cents:
type: integer
description: Every metered kind in this workspace for the period.
events:
type: integer
runs:
type: integer
description: Runs created in the period, all tiers.
metered_runs:
type: integer
included_runs:
type: integer
charged_runs:
type: integer
description: Runs created in the period that no allowance covered.
charged_cents:
type: integer
description: What the charged runs cost, and exactly the sum of `charged[].amount_cents`. This is the period total a customer is asked to believe.
run_amount_cents:
type: integer
description: The run-kind slice of `amount_cents`; equals the sum of `runs[].amount_cents`.
complimentary_runs:
type: integer
description: Runs created in the period that an operator grant comped (0041).
complimentary_cents:
type: integer
description: 'What those runs would have cost, and exactly the sum of `complimentary[].amount_cents`. Deliberately NOT part of `charged_cents`: the account was not asked to pay it.'
required:
- amount_cents
- events
- runs
- metered_runs
- included_runs
- charged_runs
- charged_cents
- run_amount_cents
licensed_articles:
type: object
properties:
month:
type: string
description: '`YYYY-MM`, the month the source ledger books against.'
articles:
type: integer
description: Licensed article purchases attributed to this workspace this month, settled or in flight.
settled_micros:
type: integer
description: What the vendor confirmed charging, in micro-USD (1000000 = $1.00). Micro-USD and not cents because a licensed page costs $0.001 to $1.00, and most purchases would round to free.
reserved_micros:
type: integer
description: 'Booked and not yet settled: a purchase in flight, held at its worst case.'
unresolved_micros:
type: integer
description: Of `reserved_micros`, the part whose outcome is unknown and awaiting reconciliation against the vendor's own record. Reported apart because an unknown outcome is neither confirmed spend nor available headroom.
required:
- month
- articles
- settled_micros
- reserved_micros
- unresolved_micros
description: 'Licensed-article spend (rung `LT`), at cost, from the fetch lane''s own source ledger — NOT from `usage_events` and NOT part of `totals`. The two ledgers are separate on purpose: one is what this workspace was charged, the other is what a vendor charged us for a page, and summing them would double-count. All zeros when nothing was licensed, which is the shipping state.'
account:
type: object
properties:
plan:
type: string
spend_cap_cents_month:
type: integer
month_to_date_cents:
type: integer
description: Account-wide charge for the current month, across EVERY workspace of the account — not just this one. Compare with `totals.amount_cents`, which is this workspace only.
required:
- plan
- spend_cap_cents_month
- month_to_date_cents
required:
- period
- workspace_id
- runs
- charged
- included
- metered
- totals
- licensed_articles
- account
headers:
Retry-After:
description: Seconds to wait before retrying. Always present on a 429.
schema:
type: integer
minimum: 0
X-RateLimit-Remaining:
description: Requests left in this window (legacy form of `RateLimit`).
schema:
type: integer
minimum: 0
X-RateLimit-Bucket:
description: 'Which bucket counted the request: `reads`, `status`, `creates`, `writes` or `public`.'
schema:
type: string
RateLimit-Policy:
description: 'The quota policy this request was counted against (draft-ietf-httpapi-ratelimit-headers): `"<bucket>";q=<requests>;w=<window seconds>`, e.g. `"reads";q=60;w=60`. Buckets: `reads` 60/min, `status` 600/min, `creates` 10/min, `writes` 20/min per credential; `public` 300/min per network on keyless routes.'
schema:
type: string
example: '"reads";q=60;w=60'
X-RateLimit-Limit:
description: Requests allowed per window in this bucket (legacy form of `RateLimit-Policy`).
schema:
type: integer
minimum: 0
X-RateLimit-Reset:
description: Unix time (seconds) when the window resets.
schema:
type: integer
minimum: 0
RateLimit:
description: 'Remaining quota in the current window: `"<bucket>";r=<remaining>;t=<seconds until reset>`, e.g. `"reads";r=57;t=41`.'
schema:
type: string
example: '"reads";r=57;t=41'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'An API key: `hr_live_…` (billed), `hr_test_…` (fixture mode, free), or `hr_chat_…` (vault chat only).'
sessionCookie:
type: apiKey
in: cookie
name: hr_session
description: Console session cookie (HttpOnly, set by `POST /v1/auth/clerk/exchange`). Mutations also require an allowlisted `Origin` and the `X-CSRF-Token` header. The requirement list names the workspace-role scope the session must carry.
clerkAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: A Clerk-issued session JWT, exchanged once for an `hr_session` cookie. Not a hyperresearch API key.
oauth2:
type: oauth2
description: 'OAuth 2.1 for agents (MCP clients): authorization code with PKCE (S256), public clients, dynamic client registration at `https://api.hyperresearch.ai/oauth/register`, discovery at `https://api.hyperresearch.ai/.well-known/oauth-authorization-server`. Access tokens (15 min) and rotating refresh tokens (30 days) are bound to the resource `https://mcp.hyperresearch.ai/mcp` and are accepted by the hosted MCP server, not by the REST operations in this document, which take an API key (`bearerAuth`). A member can only delegate scopes their workspace role holds; the token response reports the granted `scope`.'
flows:
authorizationCode:
authorizationUrl: https://api.hyperresearch.ai/oauth/authorize
tokenUrl: https://api.hyperresearch.ai/oauth/token
refreshUrl: https://api.hyperresearch.ai/oauth/token
scopes:
mcp: Connect to the hosted MCP server; every OAuth grant carries it.
vault:read: 'Read and search the workspace vault: notes, claims, sources, hubs, escalations, scholar lookups.'
runs:read: 'Read runs: status, event stream, results, reports and artifacts.'
vault:write: 'Write to the workspace vault: create/update/delete notes, ingest claims, import, resolve escalations.'
runs:write: Start, clarify, pause, resume, abort and re-run research runs (billable).
fetch: Fetch a URL into the vault on the workspace’s budget (billable; OAuth grants only, checked against the member’s role).
verify: Run citation, quote, retraction and independence verification jobs and read their results (billable).
answers:ask: 'Answers: `ask` and `search` calls billed per call from the prepaid wallet, plus reading the wallet balance (billable).'
externalDocs:
description: hyperresearch API documentation
url: https://hyperresearch.ai/docs