Skip to content
SuZhe AI
API referenceAPI overview
Get started

API overview

Integrate ChatGPT subscription activation into your application with a unified REST API. Discover plans, validate activation keys, submit tasks and track results through a consistent server-side interface.

REST APIv1JSON
Make your first requestVerify your API key and start your integration.Explore the APIFind parameters, response schemas and examples.

Before you begin#

Before you begin, prepare an issued API key, a valid activation key and the target ChatGPT account’s complete Session JSON. Handle API keys and Sessions securely on your server.

Connection details#

SettingValue
Base URLhttps://www.academicgate.org
Versionv1 (pinned in the path — see 13. Versioning)
AuthX-API-Key header
TransportHTTPS only, TLS 1.2+
Encodingapplication/json; charset=utf-8 for both request and response
Self-serve pagehttps://www.academicgate.org/store (safe to hand to end users; no API key needed; supports Plus / Pro 5x / Pro 20x / Pro 500 — see 8.3)
In one line
POST /api/v1/gptplus/redeem with card_code + session_data returns a task_id; poll GET /api/v1/gptplus/task/{task_id} every 3 seconds until finished: true.

Available endpoints#

All subscription activation endpoints. Select an endpoint for its full reference.

Next steps#

Read the integration guide, including task persistence and error handling. Before going live, review the go-live checklist.

Get started

Quick start

bash
export API_KEY="aisub_your_key"
export BASE="https://www.academicgate.org"

# 1) Verify the key works
curl -s "$BASE/api/v1/ping" -H "X-API-Key: $API_KEY"

# 2) Discover supported plans (don't hardcode key prefixes on your side)
curl -s "$BASE/api/v1/products" -H "X-API-Key: $API_KEY"

# 3) Submit an activation (session_data is the full JSON from https://chatgpt.com/api/auth/session)
curl -s -X POST "$BASE/api/v1/gptplus/redeem" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json; charset=utf-8" \
  -d '{"card_code":"PLS-XXXXXXXXXXXXXXXX","session_data":{"accessToken":"eyJ...","user":{"email":"[email protected]"}}}'

# 4) Poll for the result (every 3 seconds)
curl -s "$BASE/api/v1/gptplus/task/REPLACE_WITH_TASK_ID" -H "X-API-Key: $API_KEY"
Get started

Plans and activation keys

The plan tier is derived automatically from the key prefix. Requests must not contain any tier parameter. Send the key as-is; the system identifies which plan it belongs to and processes it accordingly.

This design eliminates one specific class of incident: if the caller specified the tier and got it wrong, the request would still count as a valid submission, consuming a use off the key with no refund. Deriving it from the prefix makes that outcome impossible.

PlanproductKey prefixExampleCredential
ChatGPT Plusgpt_plus_selfPLS-PLS-XXXXXXXXXXXXXXXXSession JSON
ChatGPT Pro 5xgpt_pro_5x_selfP5X-P5X-XXXXXXXXXXXXXXXXSession JSON
ChatGPT Pro 20xgpt_pro_20x_selfP20X-P20X-XXXXXXXXXXXXXXXXSession JSON
ChatGPT Pro 20x · Renewalgpt_pro_20x_renewPH20-PH20-XXXX-XXXX-XXXXSession JSON
ChatGPT Pro 500gpt_pro_500_selfP500-P500-XXXXXXXXXXXXXXXXSession JSON

GET /api/v1/products is the source of truth for this table. Plans and prefixes may change, and the API ships before the docs, so route and hint using the API response — never hardcode the prefix list in your code. Keys issued earlier under other prefixes still redeem fine but are not listed in the catalog — treat local checks as hints only and never reject a key on them; the server has the final say (a malformed key returns invalid_card_format and consumes nothing).

Choosing a plan#

  • Plus / Pro 5x / Pro 20x — key prefixes PLS- / P5X- / P20X-. Plus supports only accounts with no active subscription; Pro 20x (new activation supported since 2026-09-30): an account with no subscription can be activated directly, and an account already on an active Philippines (PHP) Plus / Pro 5x is upgraded (no need to cancel first; non-PH billing returns plus_not_ph_region and the key is not consumed); Pro 5x additionally supports an account that already holds an active ChatGPT Plus — it is automatically upgraded to Pro 5x (no need to cancel Plus first; Philippines/PHP Plus only — a non-PH Plus returns plus_not_ph_region and the key is not consumed). Other ineligible accounts (e.g. already on Pro 5x or higher, or a non-Plus paid subscription) are rejected (account_has_subscription) and the key is not consumed.
  • Pro 20x eligibility: three kinds of accounts are supported — (1) an account with no subscription (Free plan); (2) an account on an active Philippines (PHP) Plus; (3) an account on an active Philippines (PHP) Pro 5x ((2)/(3) are auto-upgraded, no need to cancel first). An account already on an active Pro 20x should renew after it expires; an account for which the official Pro 20x offer is not enabled returns account_not_eligible_pro20 (rarely seen) and the key is not consumed.
  • Pro 500 eligibility: key prefix P500-. Free accounts with no subscription of any kind only, and it is not an upgrade product — no upgrade from Plus / Pro 5x / Pro 20x / Go / Team and no price-difference top-up; an account whose renewal was cancelled but has not yet expired also counts as subscribed and is rejected (account_has_subscription, key not consumed). If the account already has a billing currency and it is not the Philippines (PHP), promax_billing_not_php is returned. P500- keys are delivered manually and cannot be bought online — see 2.4 Pro 500 activation notes.

Prerequisites common to all plans#

  • The target account must not have an active paid subscription (exception: Pro 5x also accepts a Philippines-region account that holds an active ChatGPT Plus, and Pro 20x also accepts a Philippines-region account that holds an active Plus / Pro 5x — both are upgraded automatically). The account's current subscription status is checked at submit time; other ineligible accounts are rejected with account_has_subscription (the key is not consumed). A subscription whose renewal was cancelled but has not yet expired still counts as active (resubmit a Plus key after it expires); an already-expired subscription counts as no subscription and can be activated normally. Pro 500 is the opposite exception and stricter: no subscription of any kind is accepted and there is no upgrade path (see 2.4).
  • session_data must be the complete session JSON of the target ChatGPT account and must contain accessToken.
  • The account must stay logged in in the browser from submission until the result arrives, otherwise the session expires and the task fails.

Duplicate top-up protection#

Topping up the same ChatGPT account again does not extend the subscription — it overwrites the current one, and the activation key is still consumed with no refund. We therefore block duplicate submissions, in two tiers based on how long ago the account was last topped up:

Since last successful top-upBehaviourerror
Within 24 hoursRejected outright; confirm_duplicate does not override thisduplicate_email_blocked
24 hours – 30 daysRejected pending confirmation; resend with confirm_duplicate: true to proceedduplicate_email_confirm
More than 30 daysAllowed—

Both return HTTP 409 with the previous record attached (the 24-hour rejection also carries blocked: true, the confirmation case carries need_confirm: true; the example below is the confirmation case):

json
{
  "success": false,
  "error": "duplicate_email_confirm",
  "detail": "This account was already topped up on 2026-09-14 08:51:21 …",
  "detail_zh": "该账号最近已充值过…",
  "duplicate": true,
  "last_product": "ChatGPT Plus",
  "last_product_zh": "ChatGPT Plus",
  "last_at": "2026-09-14 08:51:21",
  "hours_ago": 77.83,
  "need_confirm": true
}

Matching is by email across any GPT plan: an account topped up with Plus will also match when you submit Plus again, because any tier will fail or overwrite while a subscription is still active. Upgrades (Plus→Pro 5x, Plus / Pro 5x→Pro 20x) do not trigger this check. Pro 500 is not an upgrade, so the check applies to it as usual.

Integration guidance
on need_confirm, show last_product and last_at to your end user and only resend with confirm_duplicate: true after they confirm. Do not auto-resend unconditionally in code — that disables the protection entirely. On blocked, tell the user and stop; do not retry.

The check covers all of our entry points (this API, the self-serve page, and our own activation page), so a top-up made elsewhere is still detected.

Pro 500 activation notes#

P500- (Pro 500) follows rules that differ from the other plans — please review them before integrating:

  • Account eligibility: free accounts with no subscription of any kind only. Plus / Pro 5x / Pro 20x / Go / Team accounts, accounts with a cancelled-but-unexpired subscription, and accounts with unpaid invoices are all rejected (account_has_subscription); there is no upgrade and no price-difference top-up. A non-Philippines (non-PHP) billing region returns promax_billing_not_php. If the account's subscription status cannot be read at submit time, the request is rejected as well (precheck_failed) — it is never let through. All of these rejections happen before the task is accepted, so the key is not consumed.
  • How it is activated: we first set the account up on a base plan (Plus), then upgrade it to Pro 500, entirely automatically; after success we turn off auto-renewal. Because it is two steps, a failed upgrade can leave the account on the base plan (see "Failure handling" below).
  • How to obtain keys: P500- keys are delivered manually and cannot be ordered online; the catalog and the API return no prices. Contact us to buy at the agent price; once you hold a key, both this API and the self-serve page can redeem it.
  • Queueing: Pro 500 is processed one order at a time across the whole system; our own activation page, the self-serve page and all resellers share one queue, and a Pro 500 in progress may also make Pro 5x / Pro 20x tasks wait briefly during its engine step. After acceptance a task may stay queued for a long time (several minutes or more) — this is normal queueing, not a fault: keep polling and do not resubmit. A single task also takes longer than the other plans, and the duration is not fixed. Poll every 3–5 seconds and allow at least 20 minutes; after that switch to low-frequency checks or contact us, and do not mark it failed.
  • Concurrency per account: when you submit Pro 500 and the account already has a task for a PLS- / P5X- / P20X- / P500- key in accepted / queued / running state (regardless of entry point or reseller), the new submission returns 409 account_busy and the key is not consumed; submit again after the earlier task finishes.
  • Failure handling (important): never auto-resubmit after a Pro 500 failure. promax_upgrade_incomplete (base plan activated, upgrade not completed) and payment_unverified (payment result unconfirmed) both mean do not resubmit — contact us for manual handling: the account is already on the base plan, so a resubmission would be rejected because the account now has a subscription. This is the opposite of Pro 20x upgrade_incomplete (resubmit with the same key) — handle them separately, see 7.3. For all other failure codes follow the "Action" column in 7.3.
  • Failure freeze: submissions for an account are paused after 2 consecutive billing-stage failures or 3 consecutive failures of any cause; same rule as the other plans, see 7.3.
Get started

Authentication and security

Every request must carry the key. These two forms are equivalent:

http
X-API-Key: aisub_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
http
Authorization: Bearer aisub_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys are issued by us and look like aisub_ + a 32-character random string. One key identifies one client, and all records are scoped to the key — you can only see tasks you submitted.

Integration security requirements#

These are minimum requirements, not suggestions:

  1. The key lives only on your server. Putting it in a browser, app, or mini-program is equivalent to publishing it.
  2. Never log the key, full activation keys, session JSON, or access tokens. Log the task_id instead — it carries no sensitive data.
  3. If your egress IP is static, tell us and we will enable an IP allowlist for your key.
  4. If you suspect a leak, contact us immediately to revoke and reissue. A revoked key returns 401 on every request from that moment.
  5. Keys do not expire, but rotate them on whatever schedule your own key-management policy requires.
API reference

Request and response conventions

Success responses#

success: true and client_name are always present at the top level; business fields sit alongside them:

json
{
  "success": true,
  "client_name": "Example Client",
  "task_id": "a1b2c3d4e5f64789abcd1234",
  "status": "queued"
}

Error responses#

Check both the HTTP status code and the success field — never just one of them.

json
{
  "success": false,
  "error": "invalid_card_format",
  "detail": "Activation key format is invalid. Please check that the key is complete (including dashes).",
  "detail_zh": "卡密格式不正确,请核对卡密是否完整(含横线)"
}
FieldDescription
errorMachine-readable error code. Branch on this field only — never match on message text
detailEnglish message, safe to show to end users
detail_zhChinese message, safe to show to end users

Time and characters#

  • All timestamps are UTC, formatted YYYY-MM-DD HH:MM:SS.
  • Activation keys are case-insensitive; the server uppercases them before processing.
  • The request body must be a JSON object. Malformed JSON returns 400 invalid_json_body.

Rate limits#

Per key, sliding window:

ClassEndpointLimit
WritePOST /api/v1/gptplus/redeem60 / minute
ReadAll other GET endpoints600 / minute

Exceeding the limit returns 429 rate_limited. Back off exponentially (1s → 2s → 4s, capped at 30s) and retry — except for the write endpoint, see 4.5.

Retry semantics (required reading)#

redeem is not idempotent. A successful submission consumes one use of the activation key; submitting again consumes another, with no refund.

This API has no idempotency key. Therefore:

SituationCorrect action
2xx with a task_idStore the task_id and poll. Do not resubmit
4xx (parameter / key / format errors)The key was not consumed — fix and resubmit
429Not consumed — back off and resubmit
5xx / 502 / network timeout / no responseDo not resubmit blindly. First call GET /api/v1/gptplus/cdk-info?code=<key> or GET /api/v1/gptplus/jobs?card_code=<key> to check whether the task was actually created. If it was, poll it; resubmit only if it was not

All read endpoints are safe to retry freely.

Submission latency and timeouts: before accepting a task, redeem verifies the account's subscription status. It usually returns within seconds but can take up to about 80 seconds in the worst case; set the client timeout for submissions to >= 90 seconds (60 seconds would time out before the server does). After a timeout or 502 upstream_error the task may already have been created on our side — verify first as per the table above (jobs / cdk-info). If the task does exist, resubmitting with the same key returns 409 card_in_progress (with a job_id, equal to the original task_id) and does not create a second task; but never submit the same account with a different key.

API reference

API reference

Explore the subscription activation endpoints. Send every request over HTTPS with your API key in the request headers.

API reference

Verify the API key

GET/api/v1/ping

No parameters. Use it for integration self-checks and health probes.

Response example#

json
{ "success": true, "message": "pong", "client_name": "Example Client" }
API reference

Plan catalogue

GET/api/v1/products

Returns every plan and key prefix currently on sale. Fetch it once at startup and cache it; use it for UI routing and input hints. Leave prefixes outside the catalog to the server — do not reject them locally. The catalog includes Pro 500 (P500-), whose note / note_zh describe the account conditions and queueing; the catalog returns no prices.

Response example#

json
{
  "success": true,
  "client_name": "Example Client",
  "total": 5,
  "items": [
    {
      "product": "gpt_plus_self",
      "name": "ChatGPT Plus",
      "name_zh": "ChatGPT Plus",
      "key_prefix": "PLS-",
      "key_sample": "PLS-XXXXXXXXXXXXXXXX",
      "credential": "session_json",
      "note": "Only accounts without an active subscription can be activated.",
      "note_zh": "仅支持当前无订阅的账号开通。"
    }
  ]
}
API reference

Service status

GET/api/v1/gptplus/status

Live availability of the activation service. Call it before showing your order entry point — when available is false, stop the user there rather than letting a key be submitted into a maintenance window.

Response example#

json
{
  "success": true,
  "client_name": "Example Client",
  "available": true,
  "tier": "ready",
  "label": "Operational",
  "label_zh": "服务正常",
  "tone": "ok",
  "pending": 0,
  "wait_seconds": 0,
  "checked_at": "2026-09-17 10:10:50"
}

Parameters#

FieldValuesMeaning
availabletrue / falsefalse means maintenance — do not submit
tierready / busy / downbusy means a long queue; submissions still work but take longer
pendingintegerKept for compatibility; currently always 0, does not reflect queueing
wait_secondsintegerKept for compatibility; currently always 0

The result is cached server-side for 20 seconds, so there is no benefit to polling it frequently. It only reflects whether the service as a whole is available, not the queue position of an individual task (notably the single-lane Pro 500 queue) — rely on the task's own status.

API reference

Check activation key status

GET/api/v1/gptplus/cdk-info?code=PLS-XXXXXXXXXXXXXXXX

Parameters#

ParameterRequiredDescription
codeyesThe activation key. card_code and cdk are also accepted

Call this before submitting — it prevents the large majority of "this key was already used" support tickets.

Response example#

json
{
  "success": true,
  "client_name": "Example Client",
  "product": "gpt_plus_self",
  "product_name": "ChatGPT Plus",
  "product_name_zh": "ChatGPT Plus",
  "card_key": "PLS-XXXXXXXXXXXXXXXX",
  "status": "unused",
  "redeemable": true,
  "total": 0,
  "success_count": 0,
  "running_count": 0,
  "records": []
}
FieldDescription
statusunused / running (activation in progress) / used
redeemableWhether it can be submitted now. Do not submit when this is false
recordsTask history for this key, up to 20 entries; account emails are masked for privacy
Usage through any entry point is reflected here, not just your own key. To see the full details of tasks you submitted (including unmasked emails), use 5.6 and 5.7.
API reference

Submit an activation task

POST/api/v1/gptplus/redeem

Parameters#

FieldTypeRequiredDescription
card_codestringyesActivation key. cdk and cdk_code are also accepted
session_dataobject | stringyesComplete session JSON of the target account, must contain accessToken. session and session_json are also accepted; a string is parsed server-side
confirm_duplicateboolnoOnly after receiving duplicate_email_confirm, to indicate the user accepted the overwrite. Defaults to false — see 2.3 Duplicate top-up protection

Do not include any tier parameters — they are ignored. The tier is always derived from the key prefix.

Submission latency: the account is verified before the task is accepted; this usually takes seconds but can take up to about 80 seconds, so set your client timeout to >= 90 seconds (see 4.5). A successful response only means the task was accepted (queued), not that the plan is active — poll for the result. A Pro 500 task may queue for a long time after acceptance, see 2.4.

Request example#

json
{
  "card_code": "P20X-XXXXXXXXXXXXXXXX",
  "session_data": {
    "accessToken": "eyJhbGciOi...",
    "user": { "email": "[email protected]" },
    "expires": "2026-12-16T00:00:00.000Z"
  }
}

Returns 200 on success:

Response example#

json
{
  "success": true,
  "client_name": "Example Client",
  "product": "gpt_pro_20x_self",
  "product_name": "ChatGPT Pro 20x",
  "product_name_zh": "ChatGPT Pro 20x",
  "task_id": "a1b2c3d4e5f64789abcd1234",
  "status": "queued",
  "message": "Accepted",
  "message_zh": "已受理",
  "card_key": "P20X-XXXXXXXXXXXXXXXX",
  "email": "[email protected]",
  "submitted_at": "2026-09-17 10:12:00"
}

The success response carries no duplicate field; repeat submissions are blocked at submit time (returns duplicate_email_blocked / duplicate_email_confirm, see 2.3).

API reference

Poll a task

GET/api/v1/gptplus/task/{task_id}

Only tasks submitted with your key are visible; anything else returns 404 task_not_found.

Response example#

json
{
  "success": true,
  "client_name": "Example Client",
  "product": "gpt_plus_self",
  "product_name": "ChatGPT Plus",
  "task_id": "a1b2c3d4e5f64789abcd1234",
  "status": "success",
  "finished": true,
  "message": "Activation completed",
  "message_zh": "充值成功",
  "error_code": "",
  "duration_seconds": 142.6,
  "card_key": "PLS-XXXXXXXXXXXXXXXX",
  "email": "[email protected]",
  "submitted_at": "2026-09-17 10:12:00"
}

Parameters#

FieldDescription
statusqueued / running / success / failed
finishedtrue when status is success or failed. Use this as your polling stop condition
message / message_zhStatus text; on failure it is the reason, safe to show to end users
error_codeNon-empty only on failure — see 7. Error codes; whether the key was consumed and whether you may resubmit is defined in 7.3
duration_secondsAppears only after the task finishes: processing time in seconds, excluding time spent queued
activation_emailReserved field, always an empty string; use email for the account email
action_requiredPresent only when the end user must complete a one-time security check. Value: manual_captcha (see "Security check" below)
verification_urlPresent together with action_required — our hosted verification page
verification_expires_atDeadline of the verification (Unix seconds); after it passes the field disappears and the task fails

Polling guidance: every 3 seconds. A single task usually completes in 1–3 minutes. Cap polling at about 15 minutes (~300 attempts); Pro 500 queues in a single lane and takes longer — allow at least 20 minutes (see 2.4). On timeout, do not mark it failed — switch to low-frequency checks or human follow-up. Time spent waiting for the user's verification should not count against the timeout.

Security check: a small number of orders need the end user to complete a one-time security check while processing. The response then looks like:

Response example#

json
{
  "success": true,
  "status": "running",
  "finished": false,
  "action_required": "manual_captcha",
  "verification_url": "https://www.academicgate.org/verify/a1b2c3d4e5f64789abcd1234",
  "verification_expires_at": 1790000000
}
  • status stays running (not success). Hand verification_url to that order's user: embed it in an iframe on your HTTPS page (we suggest referrerpolicy="no-referrer") or open it in a new window.
  • After the user finishes, keep polling the same task_id until success / failed — closing the window does not mean success. If the link expires and a new one appears, show it again.
  • Never resubmit because of this; the link is for that order's user only — never log it or forward it to unrelated third parties.
API reference

Task history

GET/api/v1/gptplus/jobs?limit=50&offset=0&card_code=&status=&product=

Your own task records, for reconciliation and post-timeout recovery.

Parameters#

ParameterDefaultDescription
limit501–200
offset0Pagination offset
card_code—Exact-match filter by activation key
status—Exact-match filter by status — values in 6. Task state machine
product—Filter by plan — values come from the product field of GET /api/v1/products

Response example#

json
{
  "success": true,
  "client_name": "Example Client",
  "total": 128,
  "limit": 50,
  "offset": 0,
  "items": [
    {
      "task_id": "a1b2c3d4e5f64789abcd1234",
      "card_key": "PLS-XXXXXXXXXXXXXXXX",
      "product": "gpt_plus_self",
      "product_name": "ChatGPT Plus",
      "product_name_zh": "ChatGPT Plus",
      "status": "success",
      "message": "Activation completed",
      "email": "[email protected]",
      "created_at": "2026-09-17 10:12:00",
      "updated_at": "2026-09-17 10:14:31",
      "finished_at": "2026-09-17 10:14:31"
    }
  ]
}
API reference

Task state machine

code
                 ┌──────────┐
   redeem  ─────▶│  queued  │  accepted, waiting to be processed
                 └────┬─────┘
                      ▼
                 ┌──────────┐
                 │ running  │  activation in progress
                 └────┬─────┘
            ┌─────────┴─────────┐
            ▼                   ▼
      ┌──────────┐        ┌──────────┐
      │ success  │        │  failed  │   ← both terminal, finished: true
      └──────────┘        └──────────┘
  • States only move forward; a terminal state never reverts.
  • rejected appears only in the history endpoint (5.7). It means the request was rejected before entering the processing queue and no key use was consumed. It is never returned as status from the task endpoint.
  • The history endpoint (5.7) can also show two further states that exist only there: submitting (a short placeholder while the task is being created, at most about 3 minutes, after which it moves to another state) and reset (a record we reset manually — check cdk-info for whether the key can be used again, and contact us if in doubt). The status of the task endpoint is always one of queued / running / success / failed.
  • A Pro 500 task can stay queued for a long time (single queue across the whole system); this is not a fault, see 2.4.
  • Polling after a terminal state keeps returning the same result and has no side effects.
  • While running, a task may ask the end user for a security check (the response carries action_required). This is not a separate state; the task proceeds to a terminal state afterwards — see 5.6.
API reference

Error codes

Branch on the error field only.

Authentication and quota#

HTTPerrorMeaningAction
401missing_api_keyNo key suppliedCheck the header name
401invalid_api_keyKey invalid or disabledContact us
403key_not_allowedThis key is not authorised for this endpointContact us for a full key
429rate_limitedRate limit exceededExponential backoff

Parameters and activation keys#

HTTPerrorMeaningAction
400invalid_json_bodyBody is not a JSON objectCheck Content-Type and serialisation
400missing_card_codeNo activation key supplied—
400invalid_card_formatPrefix not in the supported listCompare against GET /api/v1/products
400missing_sessionNo session_data supplied—
400invalid_session_jsonSession is not a valid JSON objectAsk the user to copy the whole session page again
400session_missing_access_tokenSession has no accessTokenUser is not logged in, or copied only a fragment
400missing_task_idNo task_id supplied—
404task_not_foundTask does not exist or belongs to another keyVerify the task_id
409duplicate_email_blockedAccount was topped up within the last 24 hoursStop and inform the user; do not retry
409duplicate_email_confirmAccount was topped up within 30 days; confirmation requiredHave the user confirm, then resend with confirm_duplicate: true
400product_not_supported_hereThis key type cannot be redeemed here at the moment (for example, the entry for that plan is temporarily closed)Key not consumed; contact us

Activation business errors#

These appear in redeem error responses (with an HTTP status code) or in the error_code of a task response (a failed task: HTTP 200, status: failed); the same code can show up in both places. Whether a key was consumed and whether you may resubmit with it is defined by the "Action" column: where it says "Key not consumed" you may resubmit after fixing the cause; where it says "do not resubmit" never retry automatically.

HTTPerror / error_codeMeaningAction
400invalid_card_keyKey invalid, unknown, not sold, or out of usesVerify with cdk-info, then give the end user a definitive answer
409card_already_usedKey was already redeemed successfullyKeys are single-use — use another
409card_in_progressKey already has a task in progress, or the same account just (within about 3 hours) completed a top-up with it; the response carries a job_id (equal to the original task_id; pollable directly if it was submitted with the same key; it may be absent for a few seconds right after acceptance)Don't resubmit; poll the existing task with task (a task that already succeeded returns its success result)
409account_has_subscriptionTarget account has an active subscription (exceptions: a Pro 5x key accepts a Philippines-region account on an active Plus, and a Pro 20x key accepts a Philippines-region account on an active Plus / Pro 5x — both upgrade; an account already on an active Pro 20x should renew after it expires; Pro 500 accepts no subscription of any kind and has no upgrade path)Key not consumed; use an account without a subscription (or an upgradable account per the exceptions), or wait for expiry
409account_busy(Pro 500) The account already has a PLS- / P5X- / P20X- / P500- key task accepted / queued / running, regardless of entry point or resellerKey not consumed; wait for the earlier task to finish, then submit
409email_frozenSubmissions for this account (email) are paused after 2 consecutive billing-stage failures (failures rejected at the payment step); the counter resets after one success or when we unfreeze itKey not consumed; do not keep retrying this account — contact us to unfreeze
409session_failed_too_manySubmissions for this account (session) are paused after 3 consecutive failed activation attempts of any cause (detail_zh: 当前session失败次数过多,请联系苏哲); the counter resets after one success or when we unfreeze itKey not consumed; do not keep retrying this account — contact us to unfreeze
409plus_not_ph_regionThe account's Plus is a non-Philippines (non-PHP) subscription; the Pro 5x / Pro 20x upgrade only supports a Philippines (PHP) subscription or a free accountKey not consumed; use a free account or a Philippines (PHP) Plus account
409promax_billing_not_php(Pro 500) The account's billing region is not the Philippines (PHP)Key not consumed; use another account or contact us
409account_not_eligible_pro20The official Pro 20x offer is explicitly not enabled for this account (rarely seen)Key not consumed; retry with another account (Free, or a PHP Plus / Pro 5x)
409account_not_eligible_promax(Pro 500) The official offer is not enabled for this account (rarely seen)Key not consumed; contact us
409pro20_already_activeThe account is already on an active Pro 20x that has not expiredKey not consumed; renew after it expires
400session_expiredThe login session (Session/accessToken) has expired or is invalidKey not consumed; have the user log in to ChatGPT again, copy the latest full session, and retry
502precheck_failed(Pro 20x / Pro 500) Temporarily unable to verify the account's subscription status or eligibility (precheck service unavailable); a task is not accepted without verificationKey not consumed; retry shortly
—session_missing_access_tokenSession is missing accessTokenHave the user re-copy the full session page
—invalid_session_dataThe session was judged invalidHave the user re-export the session and retry with the new one
—invalid_product_lineKey type not currently supportedContact us to register it — usually a new key type
—invalid_recharge_methodThis key is temporarily unavailableContact us
—card_key_busy_other_productKey is in use by another taskRe-check with cdk-info shortly
200payment_declinedThe payment did not go through during activation/upgradeKey not consumed; retry later or contact us. For Pro 500, if it failed after the base plan had been activated, a resubmission is rejected with account_has_subscription — that means the account is already on the base plan; contact us for manual handling
200upgrade_incompleteActivation incomplete: the account was set up on the base plan and the upgrade still has to be completed (can occur when activating Pro 20x on a Free account; Pro 500 does not use this code — see the next row)Key not consumed; resubmit with the same key to finish, and contact support if it fails again
200promax_upgrade_incomplete(Pro 500) The base plan was activated but the upgrade to Pro 500 did not succeed; the account is now on the base planDo not resubmit (a resubmission is rejected because the account now has a subscription); contact us for manual handling. The opposite of upgrade_incomplete
200payment_unverifiedThe payment request was sent but its result could not be confirmed (can occur on any plan)Do not resubmit and do not retry with another key; contact us to reconcile
200verification_timeout / verification_failedThe security verification timed out / did not passKey not consumed; you may resubmit
200account_verification_requiredThis ChatGPT account needs additional verification and cannot be activated automatically right nowKey not consumed; contact us
200invoice_renew_requiredThe account has an unpaid outstanding invoice and cannot be activated automaticallyKey not consumed; settle the invoice or use another account, then retry
200promax_trial_unsupported(Pro 500) The account currently has an official trial offer and cannot be activated automatically; no charge was madeKey not consumed; contact us
200activation_failedCatch-all: the activation did not complete (the specific cause is not broken out externally)Key not consumed; Plus / Pro 5x / Pro 20x may be retried later with the same key, contact us if it keeps failing; for Pro 500 do not retry automatically — contact us

Codes with "—" in the HTTP column do not have a fixed place (they may be synchronous errors or task failure codes); take the status code from the actual response, and always branch on error / error_code.

For keys currently on sale (PLS- / P5X- / P20X- / P500-), invalid_card_key / account_has_subscription / session_missing_access_token are all returned before the task is accepted, so the key is not consumed and you may resend after fixing it; card_already_used / card_in_progress mean the key is already in play — do not open a new order.

Server errors#

HTTPerrorMeaningAction
500server_errorServer-side exceptionContact us with the task_id
502upstream_errorActivation service temporarily unresponsiveVerify before resubmitting — see 4.5
503unavailableService under maintenanceConfirm recovery via status before submitting
503tier_maintenancePro 5x / Pro 20x self-serve temporarily under maintenance, or Pro 500 not open at the moment (Pro 500 has its own switch and is not affected by the 5x / 20x maintenance); key not consumed; if the switch closes while a Pro 500 task is queued, it also appears as a failure code (HTTP 200)Retry later; Plus is unaffected
200system_maintenanceNot an HTTP error: it only appears as the error_code of a failed task (status: failed) and means the activation system is being updated; key not consumedWait a moment and retry with the same key
Integration guides

Recommended integration flow

code
① User places an order on your site
        │
        ▼
② GET /api/v1/gptplus/status      ← if available=false, stop here; don't take the order
        │
        ▼
③ GET /api/v1/gptplus/cdk-info    ← if redeemable=false, use a different key
        │
        ▼
④ Collect the session JSON from the user
        │
        ▼
⑤ POST /api/v1/gptplus/redeem     ← persist the task_id immediately
        │
        ▼
⑥ GET /task/{id} every 3s until finished=true
        │
        ├─ success → notify the user
        └─ failed  → map error_code to a user-facing message

Fields you must persist#

task_id, card_code, product, submission time, and last known status. These five are sufficient to diagnose any incident — none of them is optional.

Common pitfalls#

SymptomCauseFix
Fails immediately with an invalid-session errorThe user logged out of ChatGPT after copying the sessionRequire them to stay logged in during processing
A brand-new key reports as already usedThe same key was also submitted through another entry pointCheck cdk-info before submitting
Activation succeeded but the user sees no subscriptionClient-side cacheHave them refresh the ChatGPT page or sign in again
A timeout retry consumed the key twiceViolates the retry semantics in 4.5On 5xx/timeout, verify before resubmitting
Pro 20x reports an existing subscriptionThe account is already on an active Pro 20x, or on a paid plan other than Plus / Pro 5xRenew after the subscription expires; a Philippines-region Plus / Pro 5x account can upgrade directly, while non-Philippines billing returns plus_not_ph_region
Worried about double-consuming a key after a submit timeoutSubmission can take up to ~80 seconds and the client timeout was too short; the task was in fact createdUse a timeout >= 90 seconds; after a timeout check jobs, or resubmit with the same key (an existing or just-completed task returns 409 card_in_progress + job_id; just poll it as usual)
A Pro 500 task stays queued for a long timePro 500 uses a single queue across the whole systemKeep polling with an upper bound >= 20 minutes; do not resubmit (see 2.4)
A resubmission after a Pro 500 failure is rejected with account_has_subscriptionThe failure happened after the base plan was activated, so the account is already on itDo not resubmit; contact us for manual handling (see 2.4, 7.3)

Self-serve activation page (for end users)#

If you would rather not build your own submission page, you can hand https://www.academicgate.org/store straight to your end users:

  • No API key is needed by the user: they paste the activation key you gave them and their own session JSON. The page identifies the plan from the key prefix and supports Plus / Pro 5x / Pro 20x / Pro 500 (Pro 500 has its own tab, and pasting a P500- key switches to it automatically), in Chinese and English.
  • Account rules, duplicate protection, failure freezes and failure messages are the same as in this API; tasks submitted through the page appear in the key history of cdk-info, but not in your jobs records (jobs contains only tasks submitted with your key).
  • The page's per-IP limits are tighter than this API's (8 submissions / minute, 60 reads / minute); many users behind one egress IP may hit them.
  • The page waits for a result for about 6.5 minutes (about 15 minutes for Pro 500), then stops refreshing and asks the user to look the key up again later; the task itself is not affected. The page has no account-eligibility precheck — eligibility is decided at submit time, and a rejected key is not consumed.
  • The page is meant for end users in a browser only: it calls anonymous web-facing endpoints, not key-based ones, and cannot be called cross-origin from a third-party site. To embed the activation flow in your own site, call this API from your server (keep the key server-side, see 3.1); if you want an equivalent page under your own domain, contact us to discuss deployment.
Integration guides

Code examples

Python#

python
import time
import requests

BASE = "https://www.academicgate.org"
API_KEY = "aisub_your_key"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json; charset=utf-8"}

class ActivationError(Exception):
    def __init__(self, code, detail):
        super().__init__(f"{code}: {detail}")
        self.code = code

def _check(resp):
    data = resp.json()
    if resp.status_code >= 400 or not data.get("success"):
        raise ActivationError(data.get("error", "http_%d" % resp.status_code),
                              data.get("detail") or "")
    return data

def activate(card_code, session_data, timeout_seconds=900):  # Pro 500 queues longer: use >= 1200
    # 1) Service availability
    if not _check(requests.get(f"{BASE}/api/v1/gptplus/status", headers=HEADERS, timeout=30))["available"]:
        raise ActivationError("unavailable", "Activation service is under maintenance")

    # 2) Key availability
    if not _check(requests.get(f"{BASE}/api/v1/gptplus/cdk-info",
                               params={"code": card_code}, headers=HEADERS, timeout=30))["redeemable"]:
        raise ActivationError("invalid_card_key", "Key already used or currently processing")

    # 3) Submit. A success consumes a use — never retry here. It can take up to ~80s, so keep timeout >= 90; after a timeout/502 check jobs first
    job = _check(requests.post(f"{BASE}/api/v1/gptplus/redeem", headers=HEADERS, timeout=90,
                               json={"card_code": card_code, "session_data": session_data}))
    task_id = job["task_id"]
    print("submitted", task_id, job["product_name"])

    # 4) Poll
    deadline = time.time() + timeout_seconds
    while time.time() < deadline:
        time.sleep(3)
        try:
            st = _check(requests.get(f"{BASE}/api/v1/gptplus/task/{task_id}", headers=HEADERS, timeout=30))
        except (requests.RequestException, ActivationError):
            continue  # reads are safe to retry; keep polling through transient errors
        if st["finished"]:
            return st
    raise ActivationError("poll_timeout", f"No result after {timeout_seconds}s, task_id={task_id}")

Node.js#

js
const BASE = 'https://www.academicgate.org';
const API_KEY = process.env.AISUB_API_KEY;
const HEADERS = { 'X-API-Key': API_KEY, 'Content-Type': 'application/json; charset=utf-8' };

class ActivationError extends Error {
  constructor(code, detail) { super(`${code}: ${detail}`); this.code = code; }
}

async function call(path, options = {}) {
  const res = await fetch(BASE + path, { headers: HEADERS, ...options });
  const data = await res.json();
  if (!res.ok || !data.success) {
    throw new ActivationError(data.error || `http_${res.status}`, data.detail || '');
  }
  return data;
}

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function activate(cardCode, sessionData, timeoutMs = 900_000) { // Pro 500 queues longer: use >= 1_200_000
  const status = await call('/api/v1/gptplus/status');
  if (!status.available) throw new ActivationError('unavailable', 'Service under maintenance');

  const info = await call(`/api/v1/gptplus/cdk-info?code=${encodeURIComponent(cardCode)}`);
  if (!info.redeemable) throw new ActivationError('invalid_card_key', 'Key already used or processing');

  // A success consumes a use — never retry here. It can take up to ~80s, so any request timeout must be >= 90s
  const job = await call('/api/v1/gptplus/redeem', {
    method: 'POST',
    body: JSON.stringify({ card_code: cardCode, session_data: sessionData }),
  });

  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    await sleep(3000);
    try {
      const st = await call(`/api/v1/gptplus/task/${job.task_id}`);
      if (st.finished) return st;
    } catch (_) { /* reads are safe; keep polling */ }
  }
  throw new ActivationError('poll_timeout', `Timed out, task_id=${job.task_id}`);
}
Integration guides

Data handling and privacy

Session credentials#

session_data is used solely to complete that one activation and is never stored in any form. It is not written to a database, not persisted to disk, and not written to logs; it is released from memory once processing finishes.

What we retain#

For reconciliation, support, and abuse prevention we retain:

DataPurposeRetention
Activation key, task ID, plan, status and result messageReconciliation and support evidenceLong-term
The account email extracted from the sessionIdentifying which account was topped upLong-term
Caller key identifier and request IPAttribution and incident investigationLong-term

Emails are masked in the cdk-info history for all callers. Tasks you submitted yourself return the full value through the task and jobs endpoints.

Your obligations#

An end user's ChatGPT session is their personal credential. Please disclose clearly in your product how that credential is used, and comply with the data-protection requirements of your jurisdiction. We process the data you send strictly as described in this section.

Resources

Service levels and support

ItemDescription
Per-task durationTypically 1–3 minutes; longer at peak times. Cap polling at 15 minutes. Pro 500 queues in a single lane across the whole system, takes longer and varies — allow at least 20 minutes
Planned maintenancestatus returns available: false during the window — use it to gate ordering
Incident handlingReflected live through status and error codes; service resumes automatically with no action needed on your side
Technical supportAnswered during business hours; mark urgent issues as "urgent"

When raising a support request, provide the task_id, the first 6 characters of the activation key, and the UTC timestamp.

For security reasons, never send us a full activation key, session JSON, or access token through any channel. We will never ask you for them.

Contact details are in the footer of https://suzhe.ai.

Resources

Go-live checklist

Confirm each item before sending real traffic:

  • [ ] The API key lives only in server-side secret management — it is not findable in the repo or in front-end bundles
  • [ ] Logs contain no keys, full activation keys, session JSON, or access tokens
  • [ ] Both the HTTP status code and the success field are checked
  • [ ] Business logic branches on error, not on detail text
  • [ ] The 5xx/timeout path for redeem verifies before resubmitting, rather than retrying blindly
  • [ ] task_id, card_code, product, submission time, and last status are all persisted
  • [ ] Polling has an upper bound, and timeouts escalate to a human instead of being marked failed
  • [ ] The client timeout for submissions is >= 90 seconds, and after a timeout you verify (jobs / cdk-info) before deciding whether to resend
  • [ ] Pro 500: only free accounts are submitted; promax_upgrade_incomplete / payment_unverified are never auto-resubmitted; the polling cap is >= 20 minutes
  • [ ] Key prefixes come from GET /api/v1/products and are not hardcoded; prefixes outside the catalog are left to the server, not rejected locally
  • [ ] status is checked before the order entry point; orders are not taken during maintenance
  • [ ] 429 triggers exponential backoff
  • [ ] duplicate_email_confirm follows "show the user → user confirms → resend", not an unconditional auto-confirm in code
  • [ ] One full success path and one failure path have been exercised end to end
Resources

Versioning

  • The version is pinned in the path (/api/v1/...).
  • Backward-compatible changes do not bump the version and can happen at any time. Your parser must tolerate: new response fields, new error codes, new plans and key prefixes, new optional request parameters.
  • Breaking changes ship under a new path prefix (e.g. /api/v2/). The old version stays available for at least 90 days with advance notice.
  • Parse responses in a way that ignores unknown fields — do not use strict deserialisation.
Resources

Changelog

DateChange
2026-10-10Documentation addendum (no API change): clarified 409 card_in_progress — besides "the key already has a task in progress", it is also returned when the same account resubmits the same key within about 3 hours after a successful top-up (with the original job_id; polling returns the success result). This makes retrying after a network interruption or a lost response safe; after the window card_already_used is still returned.
2026-10-06ChatGPT Pro 500 added: key prefix P500-, product is gpt_pro_500_self, and the GET /api/v1/products catalog grows from 4 to 5 items; same set of endpoints, and the self-serve page is opened to it as well. Free accounts with no subscription only (no upgrade, no price-difference top-up); delivered manually, not sold online; a single queue across the whole system, so a task may wait several minutes; the failure codes promax_upgrade_incomplete / payment_unverified mean do not resubmit (the opposite of upgrade_incomplete). New error codes account_busy, promax_billing_not_php, account_not_eligible_promax, promax_trial_unsupported, promax_upgrade_incomplete — see 2.4 and 7.3. Endpoint paths and existing fields unchanged — existing integrations need no changes.
2026-10-06Documentation aligned with live behaviour (no API change): 7.3 now lists HTTP status codes and the error codes that were already live but undocumented (pro20_already_active, payment_unverified, verification_timeout, verification_failed, account_verification_required, invoice_renew_required, activation_failed), and states the thresholds of email_frozen (2 consecutive billing-stage failures) and session_failed_too_many (3 consecutive failures of any cause); card_in_progress carries a job_id; system_maintenance is corrected to be the error_code of a failed task (not an HTTP 503); submission can take up to ~80 seconds, so set client timeouts to >= 90 seconds; the task response gains duration_seconds and jobs records gain the submitting / reset states; pending / wait_seconds of status are marked as compatibility fields; the 2.3 duplicate example now shows last_product_zh / blocked; account_not_eligible_pro20 is marked as rarely seen; new section 8.3 on the self-serve page.
2026-09-30Pro 20x now supports new activation and upgrades: accounts with no subscription can be activated directly; accounts already on an active Philippines (PHP) Plus / Pro 5x are auto-upgraded to Pro 20x (no need to cancel first). An account already on an active 20x still renews after expiry. New error upgrade_incomplete (account set up on the base plan, upgrade difference still pending; key not consumed, resubmit with the same key); account_not_eligible_pro20 now means "the official offer is not enabled for this account". Endpoint paths and fields unchanged.
2026-09-25Pro 5x now additionally supports an account with an active ChatGPT Plus (auto-upgraded to Pro 5x, no need to cancel Plus first — no longer returns account_has_subscription). (The Pro 20x eligibility rules were updated on 2026-09-30 — see the row above.) Endpoint paths and fields unchanged.
2026-09-24Key prefixes updated to PLS- (Plus) / P5X- (Pro 5x) / P20X- (Pro 20x); product is gpt_plus_self / gpt_pro_5x_self / gpt_pro_20x_self (see 2, 5.2). Only accounts without an active subscription are supported; otherwise the new error account_has_subscription is returned. Also new: card_already_used / card_in_progress (see 7.3). Keys issued earlier still redeem but no longer appear in the /products catalog. Trust the live /products response — do not hardcode prefixes, and do not reject prefixes outside the catalog locally.
2026-09-24The security check is unified as action_required: "manual_captcha" + verification_url (our address), opened in an iframe or new window; status stays running — keep polling the same task_id after completion (see 5.6). No other verification type is issued.
2026-09-23API base URL changed to https://www.academicgate.org (aisubscription.vip is retired). Paths, parameters and API keys are unchanged — only the domain changes.
2026-09-17Added duplicate top-up protection: a second submission for the same account within 24 hours is rejected outright; within 30 days it requires confirm_duplicate: true. See 2.3. New error codes duplicate_email_blocked / duplicate_email_confirm.
2026-09-17Coverage extended to all GPT plans (Plus / Pro 5x / Pro 20x new and renewal). Added GET /api/v1/products; added product and related fields to cdk-info / redeem / task / jobs; added a product filter to jobs. Outbound fields tightened at the same time: internal identifiers removed, and account emails in cdk-info history are now masked (tasks you submitted still return the full value through task / jobs). Endpoint paths and existing business fields are unchanged — existing integrations need no changes.
2026-09-07Docs moved to suzhe.ai/gptapi; the API base URL is fixed at aisubscription.vip.
2026-08ChatGPT Plus key format updated.
Last updated © 2026 SuZhe AI · For contracted clients