Quickstart
One API key and one JSON envelope for 73 social, e-commerce and web data endpoints. Only successful calls that return data are charged.
Three steps to a first call
Create an API key
Create a key in the console and, if you like, limit the endpoints it can call (scopes). The full key is shown once, when you create it, so save it right away: the server keeps only a hash and cannot show it again.
export QOVIA_KEY="sk-qovia-…"
Check the keyGET
/v1/accountConfirms the API key works and returns balance, plan, per-minute limit and this month's free quota. Free, but counts toward the rate limit.
curl https://api.qovia.ai/v1/account \ -H "Authorization: Bearer $QOVIA_KEY"
Example response
{ "request_id": "00000000-0000-0000-0000-000000000000", "data": { "user": { "id": "00000000-0000-0000-0000-000000000000", "email": "you@example.com" }, "api_key": { "id": "00000000-0000-0000-0000-000000000000", "name": "production", "scopes": [ "*" ], "expires_at": null }, "billing": { "balance_usd": "10.000000", "plan": "payg", "rpm_limit": 600, "free_quota": { "limit": 1000, "used": 12, "remaining": 988, "resets_at": "2026-11-01T00:00:00+08:00" } } }, "meta": { "billed": false } }Make your first call
Try Bilibili video search (
GET /v1/raw/bilibili/search): it serves live data and runs on the free quota.curl 'https://api.qovia.ai/v1/raw/bilibili/search?keyword=%E5%8F%B0%E7%A7%AF%E7%94%B5' \ -H "Authorization: Bearer $QOVIA_KEY"
Replace
$QOVIA_KEYwith your API key{ "request_id": "00000000-0000-0000-0000-000000000000", "data": { "items": [ { "id": "soc_bil_2913ac9c522a", "src": "social", "platform": "bilibili", "lang": "zh-CN", "text": "「小白」全球芯片代工王!万字台积电发展史", "author": { "uid_hash": "ca5fa000e345", "mid": "8969156", "name": "小白测评" }, "metrics": { "plays": 689248, "likes": 20365, "saves": 7230, "comments": 1348, "danmaku": 2676 }, "extra": { "source_id": "BV1DkoeYhExZ", "url": "https://www.bilibili.com/video/BV1DkoeYhExZ", "title": "「小白」全球芯片代工王!万字台积电发展史", "published_at": "2025-03-26T18:30:00+08:00", "category": { "id": 95, "name": "数码" }, "tags": [ "芯片", "数码", "台积电", "手机芯片" ], "cover": "https://i0.hdslb.com/bfs/archive/8f62d780e4e308bafa8c9f6c4d14ce5a28b2ce0b.jpg" }, "prov": { "hash": "0xc16f694e973143c1", "ts": "2026-10-20T12:00:00+08:00", "source": "live" } } ], "cursor": 2, "has_more": true }, "meta": { "billed": false, "charged_usd": "0.000000", "used_free_quota": true, "free_quota_remaining": 999, "balance_usd": "0.000000", "empty": false } }Catalog sample, not live data
A new account's first 1,000 calls run on the free quota and cost nothing; after that the same call is charged at its price, as the
See this endpoint's full referencemetaof the paid example under Responses below shows.
Base URL and authentication
Every endpoint lives under one base URL:
https://api.qovia.aiSend the API key with every request, as a Bearer token in the Authorization header:
Authorization: Bearer $QOVIA_KEY- You can send
x-api-key: <API Key>instead; when both are present,Authorizationwins. - The key is read from these two headers only, never from the URL or a cookie.
- A missing, malformed, revoked or expired key gets 401
invalid_api_key; 30 failed attempts from one IP within a minute get 429too_many_auth_failures.
Scopes
A key carries one or more scopes, picked when you create it; calling an endpoint outside them gets 403 scope_denied.
| scope | Grants |
|---|---|
* | Every endpoint |
search | Cross-domain search |
raw:* | Every raw endpoint |
raw:<platform> | Every raw endpoint of one platform: raw:douyin, raw:bilibili, raw:xingtu, raw:tieba, raw:xiaohongshu, raw:tiktok, raw:weibo, raw:shopee, raw:amazon, raw:fliggy, raw:news, raw:cljx, raw:huahuo |
Requests
- GET endpoints
- Parameters go in the query string and are converted to their declared types: integers as digits, booleans as
true/false/1/0/yes/no, and arrays by repeating the name (?id=1&id=2). - POST endpoints
- The body is one JSON object, sent with
Content-Type: application/json. A body that is not valid JSON, or not an object, gets 400invalid_params. - Validation
- Only the listed parameters are accepted. An unknown one, a missing required one, or a wrong type or format gets 400
invalid_params, not charged. Itsmessagestarts with the parameter's name (item_id: …); a missing or unknown parameter starts it withparams: …. - Defaults
- Optional parameters you leave out get their defaults on the server (see each parameter's Default).
Responses
A success is HTTP 200 with the shared envelope. Here is a paid call, once the free quota is used up; compare it with step 3:
{
"request_id": "00000000-0000-0000-0000-000000000000",
"data": {
"items": [
{
"id": "soc_bil_2913ac9c522a",
"src": "social",
"platform": "bilibili",
"lang": "zh-CN",
"text": "「小白」全球芯片代工王!万字台积电发展史",
"author": {
"uid_hash": "ca5fa000e345",
"mid": "8969156",
"name": "小白测评"
},
"metrics": {
"plays": 689248,
"likes": 20365,
"saves": 7230,
"comments": 1348,
"danmaku": 2676
},
"extra": {
"source_id": "BV1DkoeYhExZ",
"url": "https://www.bilibili.com/video/BV1DkoeYhExZ",
"title": "「小白」全球芯片代工王!万字台积电发展史",
"published_at": "2025-03-26T18:30:00+08:00",
"category": {
"id": 95,
"name": "数码"
},
"tags": [
"芯片",
"数码",
"台积电",
"手机芯片"
],
"cover": "https://i0.hdslb.com/bfs/archive/8f62d780e4e308bafa8c9f6c4d14ce5a28b2ce0b.jpg"
},
"prov": {
"hash": "0xc16f694e973143c1",
"ts": "2026-10-20T12:00:00+08:00",
"source": "live"
}
}
],
"cursor": 2,
"has_more": true
},
"meta": {
"billed": true,
"charged_usd": "0.001500",
"used_free_quota": false,
"free_quota_remaining": 0,
"balance_usd": "9.998500",
"empty": false
}
}Catalog sample, not live data
Success
| Field | Type | Description |
|---|---|---|
request_id | string | The call's ID (its usage record), also in the X-Request-Id header and in the console usage log. |
data | object | array | null | The endpoint's data; see each endpoint's response example. An empty result may be an empty structure (e.g. empty items) or null; check meta.empty. |
meta.billed | boolean | Whether this call was charged to the balance; false for free-quota calls and empty results. |
meta.charged_usd | string | Amount charged in USD, 6 decimals; "0.000000" when nothing was charged. |
meta.used_free_quota | boolean | Whether this call used the free quota. |
meta.free_quota_remaining | integer | Free calls left this month. |
meta.balance_usd | string | Balance after settlement, USD, 6 decimals. |
meta.empty | boolean | The result is empty: not charged, and a used free call is given back. |
An empty result is still HTTP 200 with meta.empty true, not charged (a used free call is given back); data may be an empty structure or null, so check meta.empty.
Errors
| Field | Type | Description |
|---|---|---|
error.code | string | The error code; see the error table. Branch on code. |
error.message | string | A short English explanation. One code can have several messages (the error table lists them all), so do not branch on it. Never contains the data source's own error text. |
error.request_id | string | Same as the X-Request-Id header (see below). |
Response headers
| Header | Sent with | Description |
|---|---|---|
X-Request-Id | Every response except an unexpected plain-text 500 | Same as request_id in the body. For a reserved call (success, empty, or a failure after reservation) it is the usage record ID; a request refused before reservation keeps the X-Request-Id you sent, or gets a generated one. |
X-RateLimit-Limit | Success (including empty)GET /v1/accountErrors: rate_limited, too_many_unbilled, upstream_busy, upstream_timeout, upstream_error, upstream_rejected, not_found | The account's requests-per-minute limit. |
X-RateLimit-Remaining | Success (including empty)GET /v1/accountErrors: rate_limited, too_many_unbilled, upstream_busy, upstream_timeout, upstream_error, upstream_rejected, not_found | Requests left in the current 60-second window. |
X-Qovia-Charged | Success (including empty)Errors: upstream_busy, upstream_timeout, upstream_error, upstream_rejected, not_found | Charged for this call, USD, 6 decimals; 0.000000 when nothing was charged or it was refunded. |
X-Qovia-Balance | Success (including empty)Errors: upstream_busy, upstream_timeout, upstream_error, upstream_rejected, not_found | Balance after settlement, USD, 6 decimals. |
Retry-After | Errors: too_many_auth_failures, rate_limited, too_many_unbilled, endpoint_busy, upstream_busy | Seconds to wait before retrying. |
WWW-Authenticate | Errors: invalid_api_key | Bearer realm="qovia". |
Allow | Errors: method_not_allowed | The methods this path supports. |
/v1 only takes GET and POST: other methods (e.g. PUT, DELETE) get a framework 405 {"detail": "Method Not Allowed"}, and an unexpected server error may be a plain-text 500. Neither is charged.
Billing and free quota
- Only successful calls that return data are charged.
- Failures, empty results and timeouts cost nothing: the reserved charge, or free call, is given back automatically.
- 1,000 free calls a month, reset each calendar month in Asia/Singapore, on endpoints marked "Free quota applies"; free calls do not count toward the tiers.
- When the balance cannot pay for a call, nothing runs and you get 402
insufficient_balance. - Every call reports its charge in
metaand in theX-Qovia-Charged/X-Qovia-Balanceheaders.
Tiered prices
Each product is priced in marginal monthly tiers: within a calendar month, billed calls past a threshold are priced at the next tier.
| Product | Billed calls a month | Per 1k | Per call |
|---|---|---|---|
| Search | 0 – 1M | $4 | $0.004 |
| Search | 1M – 10M | $3 | $0.003 |
| Search | 10M+ | $2.5 | $0.0025 |
| Raw endpoints | 0 – 5M | $1.5 | $0.0015 |
| Raw endpoints | 5M+ | $1 | $0.001 |
Fixed-price endpoints
These endpoints are not tiered: every successful call costs a fixed price.
| Price | Free quota | Endpoints |
|---|---|---|
| $0.01 / call$10 per 1k | Not used | Xingtu (16), Xiaohongshu (11) |
| $0.0015 / call$1.5 per 1k | Not used | Cili Juxing (12), Huahuo (6) |
Rate limits
- Free accounts
- 60 req/min
- After the first top-up
- 600 req/min
- Window
- 60 s, sliding
- The per-minute limit is per account: all its API keys and the console playground share it.
- Some endpoints also have a global per-second cap (across all users; see each endpoint's Rate cap); over it you get 429
endpoint_busy, and the request does not count toward your per-minute limit. - If, in the last 10 minutes, at least 50 of your calls to data sources, and over 80% of them, came back empty, not found or rejected, you get 429
too_many_unbilled; slow down and retry. - When a 429 or 503 carries a
Retry-Afterheader, wait that many seconds before you retry. A 503endpoint_unavailablehas none: the endpoint is not live, so retrying will not help.
Errors
An error comes with its HTTP status and the error envelope:
{
"error": {
"code": "rate_limited",
"message": "rate limit exceeded",
"request_id": "00000000-0000-0000-0000-000000000000"
}
}| code | HTTP | Billing | Description |
|---|---|---|---|
invalid_api_key | 401 | Not charged | The API key is missing, malformed, unknown, revoked or expired.
|
too_many_auth_failuresRetryable | 429 | Not charged | 30 failed authentications from this IP within a minute; try again in 60 seconds.
|
account_disabled | 403 | Not charged | The account is disabled.
|
endpoint_not_found | 404 | Not charged | No such endpoint; check the method and path.
|
method_not_allowed | 405 | Not charged | The path exists but takes another method; the Allow header names it.
|
endpoint_disabled | 410 | Not charged | The endpoint has been retired.
|
scope_denied | 403 | Not charged | This API key lacks the scope the endpoint requires.
|
endpoint_unavailable | 503 | Not charged | The endpoint is not available right now (no data source connected yet, or no price configured); nothing ran.
|
invalid_params | 400 | Not charged | The parameters failed validation: a required one is missing, a type or format is wrong, an unknown one was sent, or the POST body is not a JSON object. An error in one parameter starts with its name (item_id: …); a missing or unknown parameter starts with params: ….
|
rate_limitedRetryable | 429 | Not charged | Over the account's per-minute limit (sliding 60-second window); wait for Retry-After.
|
too_many_unbilledRetryable | 429 | Not charged | In the last 10 minutes at least 50 of your calls to data sources, and over 80% of them, came back empty, not found or rejected; slow down.
|
endpoint_busyRetryable | 429 | Not charged | The endpoint's per-second capacity across all users is full; retry in 1 second. This does not count toward your per-minute limit.
|
insufficient_balance | 402 | Not charged | The balance cannot pay for this call (the free quota is used up, or the endpoint does not use it).
|
upstream_busyRetryable | 503 | Refunded | The data source is busy or cooling down; refunded. Retry after Retry-After.
|
upstream_timeout | 504 | Refunded | The data source timed out; refunded.
|
upstream_error | 502 / 503 | Refunded | The data source failed; refunded.
|
upstream_rejected | 400 | Refunded | The data source rejected these parameters (well-formed, but not accepted); refunded.
|
not_found | 404 | Refunded | The data source has no such item (deleted, or the ID does not exist); refunded.
|
reservation_expired | 504 | Refunded | The call took too long and was refunded automatically; no data is returned.
|
Branch on error.code; message is for people and can be worded several ways.
For AI agents and tools
AI tools can call these endpoints directly over MCP, and the same catalog is published in machine-readable form for code generators:
https://api.qovia.ai/mcpOpenAPI 3.1Parameters, response examples and errors of all 73 endpoints.https://qovia.ai/openapi.jsonllms.txtAn index for language models: auth, billing and one line per endpoint.https://qovia.ai/llms.txt