XiaoZhuJian for developers
There are two ways in, and they reach the same things. Both make cards through the pipeline the app uses, count them against the same monthly allowance, and put them in the same place — your cards, where you can study them, download them as an Anki deck, or put them in a stack. Each endpoint below shows the REST call and, where there is one, the MCP tool an assistant would use for the same thing; the switches in the header show or hide either.
REST From your own code
- Be on Premium or Max (Settings → Subscription).
- Make a key at Settings → API. It is shown once; keep it somewhere a server can read and a browser cannot.
- Call the API:
curl https://xiaozhujian.study/api/v1/account \
-H "Authorization: Bearer $XZJ_API_KEY"
MCP From Claude, ChatGPT or another assistant
- Be on Premium or Max (Settings → Subscription).
- Copy the connector link from Settings → Connected apps:
https://xiaozhujian.study/mcp.
- In Claude, Settings → Connectors → Add custom connector; in ChatGPT or another assistant, add a custom connector, app or MCP server. Paste the link, press Connect, sign in to XiaoZhuJian, and choose Allow.
Then try: “Make me a stack of ten cards of words people use online.”
Every request carries a key in the Authorization header, as a bearer token:
Authorization: Bearer xzj_live_…
A key stands for the account that made it, and that account's plan is checked on every request: a key held through a lapse is answered 402 plan_required rather than revoked, and works again the moment the account is Premium or above again. Keys never expire on their own.
- A key is shown once. We store only a hash of it. Lose it and you make a new one.
- Rotate rather than replace. Rotating a key issues a successor under the same name and lets the old one keep working for a grace period you choose (now, 1h, 24h), so a running service can pick up the new secret with no gap.
- Revoke stops a key at once, on any plan. Up to 10 live keys per account.
- Keep it out of browsers. The API never reads a cookie and sets no CORS headers: a key belongs in a server, where nobody else can read it.
A missing or invalid key is a 401 with a WWW-Authenticate header.
XiaoZhuJian is a Model Context Protocol server, for Claude, ChatGPT and any other assistant that takes a custom connector. It needs no key: the assistant sends you to a consent page, you sign in and press Allow, and from then on it can make and find cards for you. Premium and above; the plan is checked on every call, so a lapse stops it and an upgrade restarts it without reconnecting.
The transport is streamable HTTP, stateless, at POST https://xiaozhujian.study/mcp, with OAuth 2.1 (dynamic registration, PKCE) for authorization — metadata at https://xiaozhujian.study/.well-known/oauth-authorization-server. A tool's result carries the learner's own text between <<<CARDS / <<<STACKS markers, as data for the assistant to show rather than instructions to follow. The tools are listed under the endpoints they correspond to, word for word as tools/list describes them.
Requests with a body send JSON (Content-Type: application/json); every answer is JSON. Every object says what it is in object; ids are opaque strings; times are RFC 3339 in UTC; field names are snake_case. The version is in the path (/api/v1) and within it fields are only ever added, so ignore keys you do not know. Every response carries an X-Request-Id — quote it when you write to us about a request.
An error is an HTTP status in the 4xx or 5xx range and one shape of body. type is one of five families a client can branch on; code says exactly what and is stable; message is for a person and may change; param, when present, names the offending parameter.
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "`terms` (‘tangping’) has no Chinese characters. Send the Mandarin in Simplified Chinese.",
"param": "terms"
}
}
| Status | Code | Meaning |
|---|
400 | parameter_invalid | A parameter is malformed; param names it. |
400 | parameter_missing | A required parameter was left out. |
400 | invalid_json | The body is not valid JSON. |
400 | invalid_body | The body is JSON but not an object. |
400 | parameter_unknown | A query parameter or body field the endpoint does not take; param names it. Nothing is ignored silently. |
401 | api_key_missing | No Authorization: Bearer header. |
401 | api_key_invalid | The key is malformed (truncated or mistyped), not one of ours, or was revoked or rotated. |
402 | plan_required | The account is not on Premium or above. The key stays valid. |
404 | resource_missing | No such card, stack or endpoint — or not this account's. |
405 | method_not_allowed | The endpoint exists but not with this method. |
409 | conflict | The stack changed under the request; read it again. |
409 | stack_limit_reached | The account already holds 100 stacks. |
409 | too_many_cards | The stack would exceed 100 cards. |
429 | rate_limit_exceeded | More than 120 requests in a minute; wait for Retry-After. |
429 | monthly_limit_exceeded | The month's generation allowance is spent. |
502 | card_not_made | The card could not be made just now; retry in a minute. |
503 | service_unavailable | A dependency of ours did not answer; retry. |
500 | internal_error | Our fault. The X-Request-Id header identifies the request. |
The types: invalid_request_error (4xx about the request), authentication_error (401), permission_error (402, 403), rate_limit_error (429) and api_error (5xx).
A list endpoint answers a list object: data holds the page, has_more says whether another follows, and url is the endpoint. Pages are cursor-based: pass the last object's id as starting_after to get the next, and limit (1–100, default 20) for the page size. A card search is ranked, not paged.
{
"object": "list",
"url": "/api/v1/cards",
"has_more": true,
"data": [
"…"
]
}
Two limits apply. Requests: 120 a minute per key; past that, 429 rate_limit_exceeded with a Retry-After header. Generations: every card made counts against the account's monthly allowance, the same one the app spends; Retrieve the account says what is left, and a request that would overrun it is 429 monthly_limit_exceeded before anything is made.
One flashcard: its terms (each with its pinyin, a short translation and, for a term typed in another language, the Chinese rendering the sentence used), the sentence with its pinyin, translation and word-by-word segments, the audio (a public URL, or null for a card with none), what it was made with, and its review state in the app's spaced-repetition schedule. is_sentence marks a card whose one term is itself a sentence.
{
"object": "card",
"id": "cs_01j9x7d2k8b3",
"terms": [
{
"text": "躺平",
"pinyin": "tǎng píng",
"translation": "to lie flat; to opt out",
"rendering": null
}
],
"is_sentence": false,
"sentence": {
"text": "工作太累了,他决定躺平一段时间。",
"pinyin": "gōng zuò tài lèi le tā jué dìng tǎng píng yí duàn shí jiān",
"translation": "Work was exhausting, so he decided to lie flat for a while.",
"segments": [
{
"text": "工作",
"pinyin": "gōng zuò"
},
{
"text": "太",
"pinyin": "tài"
},
{
"text": "累",
"pinyin": "lèi"
},
{
"text": "了",
"pinyin": "le"
},
{
"text": ",",
"pinyin": ""
},
{
"text": "他",
"pinyin": "tā"
},
{
"text": "决定",
"pinyin": "jué dìng"
},
{
"text": "躺平",
"pinyin": "tǎng píng"
},
{
"text": "一段",
"pinyin": "yí duàn"
},
{
"text": "时间",
"pinyin": "shí jiān"
},
{
"text": "。",
"pinyin": ""
}
]
},
"audio": {
"url": "https://audio.example.com/audio/static/output_7f3a….mp3",
"model": "qwen-audio-3.0-tts-flash",
"voice": "Cherry"
},
"language": "zh-CN",
"level": "HSK4",
"style": "general",
"model": "qwen3.7-plus",
"context": null,
"idiomatic_notes": [],
"review": {
"state": "new",
"due": null,
"review_count": 0,
"lapses": 0,
"last_reviewed_at": null
},
"url": "https://xiaozhujian.study/cards/cs_01j9x7d2k8b3",
"created_at": "2026-10-06T09:14:02.511000+00:00",
"updated_at": "2026-10-06T09:14:02.511000+00:00"
}
RESTGET/api/v1/cards
Every card the account has made, newest first — or, with query, the cards it finds: cards whose Chinese, pinyin (tone marks optional) or terms match come first, then ones matched only by their English, newest first within each. A search is ranked rather than paged, so starting_after cannot be combined with it.
| Parameter | Type | Description |
|---|
queryquery | string | Chinese, pinyin or English to search for. At most 200 characters. |
limitquery | integer | How many to return, 1–100. Default 20. |
starting_afterquery | string | A card id; the page after it. Not with query. |
Returns A list of card objects.
Request
curl "https://xiaozhujian.study/api/v1/cards?limit=2" \
-H "Authorization: Bearer $XZJ_API_KEY"
Response
{
"object": "list",
"url": "/api/v1/cards",
"has_more": true,
"data": [
{
"object": "card",
"id": "cs_01j9x7d2k8b3",
"terms": [
{
"text": "躺平",
"pinyin": "tǎng píng",
"translation": "to lie flat; to opt out",
"rendering": null
}
],
"is_sentence": false,
"sentence": {
"text": "工作太累了,他决定躺平一段时间。",
"pinyin": "gōng zuò tài lèi le tā jué dìng tǎng píng yí duàn shí jiān",
"translation": "Work was exhausting, so he decided to lie flat for a while.",
"segments": [
{
"text": "工作",
"pinyin": "gōng zuò"
},
{
"text": "太",
"pinyin": "tài"
},
{
"text": "累",
"pinyin": "lèi"
},
{
"text": "了",
"pinyin": "le"
},
{
"text": ",",
"pinyin": ""
},
{
"text": "他",
"pinyin": "tā"
},
{
"text": "决定",
"pinyin": "jué dìng"
},
{
"text": "躺平",
"pinyin": "tǎng píng"
},
{
"text": "一段",
"pinyin": "yí duàn"
},
{
"text": "时间",
"pinyin": "shí jiān"
},
{
"text": "。",
"pinyin": ""
}
]
},
"audio": {
"url": "https://audio.example.com/audio/static/output_7f3a….mp3",
"model": "qwen-audio-3.0-tts-flash",
"voice": "Cherry"
},
"language": "zh-CN",
"level": "HSK4",
"style": "general",
"model": "qwen3.7-plus",
"context": null,
"idiomatic_notes": [],
"review": {
"state": "new",
"due": null,
"review_count": 0,
"lapses": 0,
"last_reviewed_at": null
},
"url": "https://xiaozhujian.study/cards/cs_01j9x7d2k8b3",
"created_at": "2026-10-06T09:14:02.511000+00:00",
"updated_at": "2026-10-06T09:14:02.511000+00:00"
},
"…"
]
}
RESTPOST/api/v1/cards
Make one card: an example sentence at the account's level, pinyin, a translation in the account's interface language and native-speaker audio, saved to the account's cards. The call is synchronous and takes ten to thirty seconds, so give it a generous timeout and do not retry one that is still running: each card counts one generation against the monthly allowance. A stack named in stack is checked before anything is made.
| Parameter | Type | Description |
|---|
terms requiredbody | array | One to 4 objects of {chinese, english?}: the Simplified Chinese word or phrase, and an optional English gloss that steers the sentence toward that meaning. Several terms share the card and its one sentence; a card costs one generation however many it has. |
stackbody | string | The id of one of the account's saved stacks to add the card to as well. |
contextbody | string | What the sentence should be about, at most 200 characters. Stored on the card and returned exactly as sent. The account's About you, and the stack's own context when stack is given, are read as well. |
Returns The card, with stack (the stack's id) when one was given. Status 201.
Request
curl https://xiaozhujian.study/api/v1/cards \
-H "Authorization: Bearer $XZJ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"terms": [{"chinese": "躺平", "english": "to lie flat; to opt out"}]}'
Response
{
"object": "card",
"id": "cs_01j9x7d2k8b3",
"terms": [
{
"text": "躺平",
"pinyin": "tǎng píng",
"translation": "to lie flat; to opt out",
"rendering": null
}
],
"is_sentence": false,
"sentence": {
"text": "工作太累了,他决定躺平一段时间。",
"pinyin": "gōng zuò tài lèi le tā jué dìng tǎng píng yí duàn shí jiān",
"translation": "Work was exhausting, so he decided to lie flat for a while.",
"segments": [
{
"text": "工作",
"pinyin": "gōng zuò"
},
{
"text": "太",
"pinyin": "tài"
},
{
"text": "累",
"pinyin": "lèi"
},
{
"text": "了",
"pinyin": "le"
},
{
"text": ",",
"pinyin": ""
},
{
"text": "他",
"pinyin": "tā"
},
{
"text": "决定",
"pinyin": "jué dìng"
},
{
"text": "躺平",
"pinyin": "tǎng píng"
},
{
"text": "一段",
"pinyin": "yí duàn"
},
{
"text": "时间",
"pinyin": "shí jiān"
},
{
"text": "。",
"pinyin": ""
}
]
},
"audio": {
"url": "https://audio.example.com/audio/static/output_7f3a….mp3",
"model": "qwen-audio-3.0-tts-flash",
"voice": "Cherry"
},
"language": "zh-CN",
"level": "HSK4",
"style": "general",
"model": "qwen3.7-plus",
"context": null,
"idiomatic_notes": [],
"review": {
"state": "new",
"due": null,
"review_count": 0,
"lapses": 0,
"last_reviewed_at": null
},
"url": "https://xiaozhujian.study/cards/cs_01j9x7d2k8b3",
"created_at": "2026-10-06T09:14:02.511000+00:00",
"updated_at": "2026-10-06T09:14:02.511000+00:00"
}
RESTGET/api/v1/cards/{card_id}
One card of the account's, by id.
| Parameter | Type | Description |
|---|
card_id requiredpath | string | The card's id. |
Returns A card object.
Request
curl https://xiaozhujian.study/api/v1/cards/cs_01j9x7d2k8b3 \
-H "Authorization: Bearer $XZJ_API_KEY"
Response
{
"object": "card",
"id": "cs_01j9x7d2k8b3",
"terms": [
{
"text": "躺平",
"pinyin": "tǎng píng",
"translation": "to lie flat; to opt out",
"rendering": null
}
],
"is_sentence": false,
"sentence": {
"text": "工作太累了,他决定躺平一段时间。",
"pinyin": "gōng zuò tài lèi le tā jué dìng tǎng píng yí duàn shí jiān",
"translation": "Work was exhausting, so he decided to lie flat for a while.",
"segments": [
{
"text": "工作",
"pinyin": "gōng zuò"
},
{
"text": "太",
"pinyin": "tài"
},
{
"text": "累",
"pinyin": "lèi"
},
{
"text": "了",
"pinyin": "le"
},
{
"text": ",",
"pinyin": ""
},
{
"text": "他",
"pinyin": "tā"
},
{
"text": "决定",
"pinyin": "jué dìng"
},
{
"text": "躺平",
"pinyin": "tǎng píng"
},
{
"text": "一段",
"pinyin": "yí duàn"
},
{
"text": "时间",
"pinyin": "shí jiān"
},
{
"text": "。",
"pinyin": ""
}
]
},
"audio": {
"url": "https://audio.example.com/audio/static/output_7f3a….mp3",
"model": "qwen-audio-3.0-tts-flash",
"voice": "Cherry"
},
"language": "zh-CN",
"level": "HSK4",
"style": "general",
"model": "qwen3.7-plus",
"context": null,
"idiomatic_notes": [],
"review": {
"state": "new",
"due": null,
"review_count": 0,
"lapses": 0,
"last_reviewed_at": null
},
"url": "https://xiaozhujian.study/cards/cs_01j9x7d2k8b3",
"created_at": "2026-10-06T09:14:02.511000+00:00",
"updated_at": "2026-10-06T09:14:02.511000+00:00"
}
RESTDELETE/api/v1/cards/{card_id}
Delete a card for good, with its review history. The row is gone when the call returns; its audio follows. A stack that holds a copy of the card keeps the copy.
| Parameter | Type | Description |
|---|
card_id requiredpath | string | The card's id. |
Returns An object with the card's id and deleted: true.
Request
curl -X DELETE https://xiaozhujian.study/api/v1/cards/cs_01j9x7d2k8b3 \
-H "Authorization: Bearer $XZJ_API_KEY"
Response
{
"object": "card",
"id": "cs_01j9x7d2k8b3",
"deleted": true
}
A saved stack: a named, versioned list of cards. cards is present when one stack is retrieved or written and absent in a list; each entry is a stack_card, a snapshot of a card as it was when added, with card naming the card it came from (or null once that card is deleted — the stack keeps its copy). is_public cannot be set through the API; publishing is a decision made in the app.
{
"object": "stack",
"id": "ps_4b2f9c1e7a",
"name": "Internet slang",
"description": "Words people use online.",
"is_public": false,
"version": 1,
"card_count": 1,
"context": null,
"review_status": "under_review",
"folder": null,
"url": "https://xiaozhujian.study/stacks/ps_4b2f9c1e7a",
"created_at": "2026-10-06T09:15:40.102000+00:00",
"updated_at": "2026-10-06T09:15:40.102000+00:00",
"cards": [
{
"object": "stack_card",
"key": "k_8c1d2e",
"card": "cs_01j9x7d2k8b3",
"terms": [
{
"text": "躺平",
"pinyin": "tǎng píng",
"translation": "to lie flat; to opt out",
"rendering": null
}
],
"is_sentence": false,
"sentence": {
"text": "工作太累了,他决定躺平一段时间。",
"pinyin": "gōng zuò tài lèi le tā jué dìng tǎng píng yí duàn shí jiān",
"translation": "Work was exhausting, so he decided to lie flat for a while.",
"segments": [
"…"
]
},
"audio": {
"url": "https://audio.example.com/audio/stacks/ps_4b2f…/k_8c1d2e.mp3"
}
}
]
}
RESTGET/api/v1/stacks
The account's saved stacks, most recently edited first, without their cards — retrieve one for those.
| Parameter | Type | Description |
|---|
limitquery | integer | How many to return, 1–100. Default 20. |
starting_afterquery | string | A stack id; the page after it. |
Returns A list of stack objects, without cards.
Request
curl https://xiaozhujian.study/api/v1/stacks \
-H "Authorization: Bearer $XZJ_API_KEY"
Response
{
"object": "list",
"url": "/api/v1/stacks",
"has_more": false,
"data": [
{
"object": "stack",
"id": "ps_4b2f9c1e7a",
"name": "Internet slang",
"description": "Words people use online.",
"is_public": false,
"version": 1,
"card_count": 1,
"context": null,
"review_status": "under_review",
"folder": null,
"url": "https://xiaozhujian.study/stacks/ps_4b2f9c1e7a",
"created_at": "2026-10-06T09:15:40.102000+00:00",
"updated_at": "2026-10-06T09:15:40.102000+00:00"
}
]
}
RESTPOST/api/v1/stacks
Save a private stack: a name, an optional description, and its cards in order — each either a card already made ({id}) or one to make now ({terms}, exactly as Create a card takes them). At most 10 cards can be made in one request, each costing a generation; the stack's name and description are the topic of every sentence. A card that could not be made is listed in failed_cards and the stack is saved without it.
| Parameter | Type | Description |
|---|
name requiredbody | string | At most 120 characters. |
descriptionbody | string | At most 2000 characters. |
cardsbody | array | Up to 100 of {id} or {terms}. May be left out for an empty stack. |
Returns The stack, with its cards. Status 201.
Request
curl https://xiaozhujian.study/api/v1/stacks \
-H "Authorization: Bearer $XZJ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Internet slang", "description": "Words people use online.",
"cards": [{"terms": [{"chinese": "躺平"}]}, {"id": "cs_01j9x7d2k8b3"}]}'
Response
{
"object": "stack",
"id": "ps_4b2f9c1e7a",
"name": "Internet slang",
"description": "Words people use online.",
"is_public": false,
"version": 1,
"card_count": 1,
"context": null,
"review_status": "under_review",
"folder": null,
"url": "https://xiaozhujian.study/stacks/ps_4b2f9c1e7a",
"created_at": "2026-10-06T09:15:40.102000+00:00",
"updated_at": "2026-10-06T09:15:40.102000+00:00",
"cards": [
{
"object": "stack_card",
"key": "k_8c1d2e",
"card": "cs_01j9x7d2k8b3",
"terms": [
{
"text": "躺平",
"pinyin": "tǎng píng",
"translation": "to lie flat; to opt out",
"rendering": null
}
],
"is_sentence": false,
"sentence": {
"text": "工作太累了,他决定躺平一段时间。",
"pinyin": "gōng zuò tài lèi le tā jué dìng tǎng píng yí duàn shí jiān",
"translation": "Work was exhausting, so he decided to lie flat for a while.",
"segments": [
"…"
]
},
"audio": {
"url": "https://audio.example.com/audio/stacks/ps_4b2f…/k_8c1d2e.mp3"
}
}
]
}
RESTGET/api/v1/stacks/{stack_id}
One of the account's saved stacks, with its cards as the stack holds them: snapshots taken when each card was added, each naming the card it came from.
| Parameter | Type | Description |
|---|
stack_id requiredpath | string | The stack's id. |
Returns A stack object, with cards.
Request
curl https://xiaozhujian.study/api/v1/stacks/ps_4b2f9c1e7a \
-H "Authorization: Bearer $XZJ_API_KEY"
Response
{
"object": "stack",
"id": "ps_4b2f9c1e7a",
"name": "Internet slang",
"description": "Words people use online.",
"is_public": false,
"version": 1,
"card_count": 1,
"context": null,
"review_status": "under_review",
"folder": null,
"url": "https://xiaozhujian.study/stacks/ps_4b2f9c1e7a",
"created_at": "2026-10-06T09:15:40.102000+00:00",
"updated_at": "2026-10-06T09:15:40.102000+00:00",
"cards": [
{
"object": "stack_card",
"key": "k_8c1d2e",
"card": "cs_01j9x7d2k8b3",
"terms": [
{
"text": "躺平",
"pinyin": "tǎng píng",
"translation": "to lie flat; to opt out",
"rendering": null
}
],
"is_sentence": false,
"sentence": {
"text": "工作太累了,他决定躺平一段时间。",
"pinyin": "gōng zuò tài lèi le tā jué dìng tǎng píng yí duàn shí jiān",
"translation": "Work was exhausting, so he decided to lie flat for a while.",
"segments": [
"…"
]
},
"audio": {
"url": "https://audio.example.com/audio/stacks/ps_4b2f…/k_8c1d2e.mp3"
}
}
]
}
RESTDELETE/api/v1/stacks/{stack_id}
Delete a saved stack with every version of it. The cards stay in the account's cards.
| Parameter | Type | Description |
|---|
stack_id requiredpath | string | The stack's id. |
Returns An object with the stack's id and deleted: true.
Request
curl -X DELETE https://xiaozhujian.study/api/v1/stacks/ps_4b2f9c1e7a \
-H "Authorization: Bearer $XZJ_API_KEY"
Response
{
"object": "stack",
"id": "ps_4b2f9c1e7a",
"deleted": true
}
RESTPOST/api/v1/stacks/{stack_id}/cards
Add cards already made to the end of a stack, as a new version. A card already in the stack is refreshed where it stands rather than added twice.
| Parameter | Type | Description |
|---|
stack_id requiredpath | string | The stack's id. |
cards requiredbody | array | Card ids, in order. |
Returns The stack, with its cards.
Request
curl https://xiaozhujian.study/api/v1/stacks/ps_4b2f9c1e7a/cards \
-H "Authorization: Bearer $XZJ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cards": ["cs_01j9x7d2k8b3"]}'
Response
{
"object": "stack",
"id": "ps_4b2f9c1e7a",
"name": "Internet slang",
"description": "Words people use online.",
"is_public": false,
"version": 1,
"card_count": 1,
"context": null,
"review_status": "under_review",
"folder": null,
"url": "https://xiaozhujian.study/stacks/ps_4b2f9c1e7a",
"created_at": "2026-10-06T09:15:40.102000+00:00",
"updated_at": "2026-10-06T09:15:40.102000+00:00",
"cards": [
{
"object": "stack_card",
"key": "k_8c1d2e",
"card": "cs_01j9x7d2k8b3",
"terms": [
{
"text": "躺平",
"pinyin": "tǎng píng",
"translation": "to lie flat; to opt out",
"rendering": null
}
],
"is_sentence": false,
"sentence": {
"text": "工作太累了,他决定躺平一段时间。",
"pinyin": "gōng zuò tài lèi le tā jué dìng tǎng píng yí duàn shí jiān",
"translation": "Work was exhausting, so he decided to lie flat for a while.",
"segments": [
"…"
]
},
"audio": {
"url": "https://audio.example.com/audio/stacks/ps_4b2f…/k_8c1d2e.mp3"
}
}
]
}
The account behind the key, as its usage: cards made today and this month, the generations spent and left of the monthly allowance, and when it resets.
{
"object": "account",
"usage": {
"cards_this_month": 42,
"cards_today": 3,
"generations_used": 57,
"generations_limit": 1000,
"generations_remaining": 943,
"resets_at": "2026-11-01T00:00:00+00:00"
}
}
RESTGET/api/v1/account
What the account the key belongs to has spent of this month's allowance, and when it resets.
Returns An account object.
Request
curl https://xiaozhujian.study/api/v1/account \
-H "Authorization: Bearer $XZJ_API_KEY"
Response
{
"object": "account",
"usage": {
"cards_this_month": 42,
"cards_today": 3,
"generations_used": 57,
"generations_limit": 1000,
"generations_remaining": 943,
"resets_at": "2026-11-01T00:00:00+00:00"
}
}