Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: getcheapgames Account API
description: Game key price comparison. All money is integer cents; rankings are on the price each store lists, without the service fee a marketplace adds at checkout.
version: 0.1.0
tags:
- name: Account
paths:
/api/account/me:
get:
tags:
- Account
summary: Get Me
description: 'The signed-in account: what lib/session.ts asks for at boot, and the account page reads.'
operationId: get_me_api_account_me_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/MeOut'
/api/account/username:
post:
tags:
- Account
summary: Post Username
description: 'A new username: the account as it now is, or 422 `invalid-username` for
its shape and 409 `username-taken` for one in use (any case) or reserved.'
operationId: post_username_api_account_username_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/MeOut'
/api/account/platforms:
post:
tags:
- Account
summary: Post Platforms
description: '"My platforms", all of them at once: `{platforms: [...]}`, any of
gcg.platforms.FAMILIES'' four, the empty list for every platform. The
account as it now is, as `POST /username` answers, so the page that
saved it hands it to lib/session.ts and the listings follow at once.
Kept each once and in FAMILIES'' order, however it was sent. 422
`invalid-platform` for anything but a list of the four''s exact names,
and nothing is saved: a client that sent "ps5" meant a choice, and
saving the rest of the list would answer it with a different one.
No password and no code: this changes what the account is shown, not
how it is reached. The most a stolen cookie does with it is make the
owner''s choice for them, which the owner sees, and undoes, on the
account page.'
operationId: post_platforms_api_account_platforms_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/MeOut'
/api/account/notifications:
get:
tags:
- Account
summary: Get Notifications
description: The account page's email switches, and the address they are about.
operationId: get_notifications_api_account_notifications_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationsOut'
post:
tags:
- Account
summary: Post Notifications
description: 'One switch, `{key, on, cc}`: `key` one of `NOTIFICATION_KEYS`, `on` true
or false, `cc` the storefront the page is on, which a list joined here
then writes its emails for. The switches as they now are, as `GET`
answers them, so the page shows what was saved.
A list''s switch is the address''s row on it, the very one its emails''
links change (`alerts.join_confirmed`, `alerts.leave`): on is on the
list at once, with no confirmation email — the account''s own
verification was one — and off is the unsubscribe the email''s link
makes. The price alerts'' is `User.price_alerts`: off holds the alerts and
keeps every target.
409 `verify-first` without a confirmed address: there is no inbox yet to
speak for (en.ts says which is missing, from `GET`). 422
`invalid-notification` for a key that is not one, or an `on` that is not
a boolean, and nothing changes. No password and no code, as `POST
/platforms`: this changes what the inbox is sent, not how the account is
reached, and the owner sees the switch and turns it back.'
operationId: post_notifications_api_account_notifications_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationsOut'
/api/account/email:
post:
tags:
- Account
summary: Post Email
description: 'Ask for a new address: a confirmation goes to it, and nothing changes
until its button is pressed (`/email/confirm`). The password where the
account has one, a current code where the second factor is on.
`sent` whatever became of it — the address free and mailed, another
account''s, the account''s own, declined, or over the throttle — as en.ts
`account.emailChangeSent` words it ("If that address is free…"): this
form must not be a way to ask which addresses have accounts, password
or not. The one place `taken` is said is the confirmation, to whoever
holds the link, when the address has been claimed since.'
operationId: post_email_api_account_email_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/StatusOut'
/api/account/email/confirm:
post:
tags:
- Account
summary: Post Email Confirm
description: 'The new inbox''s link: `ok` with the address changed and confirmed,
`taken` when another account has claimed it since it was asked for,
`expired` after a day, `invalid` for anything else — a used link
included, as the token was signed under the address it replaces.
No session needed: the link may well be opened on another device, and
its token is the authentication, as every mail link''s is. The address
the account had hears of the change (`compose_changed`) when it was a
confirmed one: an unconfirmed address may be a stranger''s.'
operationId: post_email_confirm_api_account_email_confirm_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/StatusOut'
/api/account/email/code:
post:
tags:
- Account
summary: Post Email Code
description: 'The code from a confirmation email, typed on the page that asked for it
(the welcome tour, the account page): `{code, email?, cc}`. What it
confirms is what the email was for (gcg/accounts/email_codes.py):
- an address change (`POST /email`): the address changes, exactly as
the link''s `/email/confirm` changes it (`_move_to`, the same `taken`,
the same notice to an old confirmed address, news leaving it);
- the account''s own address, not yet confirmed (the sign-up''s
verification, `/api/auth/verify/resend`): it is confirmed, exactly as
the link''s `/api/auth/verify` confirms it — every provider linked
before that unlinked, as the inbox has only now been proven
(`users.confirm_first_time`).
Only the signed-in account''s own code is looked at, never another''s.
`email`, when the form sends it, must be the address the code was sent
to. `ok`; `invalid` for a wrong code (a try counted), none waiting, or
one for another address; `expired` after fifteen minutes; `too-many`
after five wrong guesses, when only a new email''s code will do; `taken`
when another account has claimed the address since. And 429 `too-many`
past `_CHECKS`, which counts every try here with the password''s and the
second factor''s, as each is a guess.
No password and no code from the app: the six digits are the proof, of
the inbox, which is what `POST /email` asked the password for in the
first place. Committed before the answer: the page asks for `/me` at
once, and must find the address confirmed.'
operationId: post_email_code_api_account_email_code_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/StatusOut'
/api/account/password:
post:
tags:
- Account
summary: Post Password
description: 'A new password, for the current one (and a current code where the
second factor is on). Every other session ends — whoever else was in is
out — and this one stays. The address hears of it, when confirmed.
409 `no-password` for an account without one: a first password is set
from the reset link (module docstring), which proves the inbox.'
operationId: post_password_api_account_password_post
responses:
'204':
description: Successful Response
/api/account/totp/start:
post:
tags:
- Account
summary: Post Totp Start
description: 'Begin an enrolment: a new seed, as a QR code, a key to type and an
otpauth URI, and the sealed blob `/totp/confirm` hands back with the
first code. Nothing is stored until then, and the blob lasts ten minutes.'
operationId: post_totp_start_api_account_totp_start_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/TotpStartOut'
/api/account/totp/confirm:
post:
tags:
- Account
summary: Post Totp Confirm
description: 'The first code from the app turns the second factor on, and the eight
recovery codes come back this once. 410 `setup-expired` for a blob over
ten minutes old, altered, or another account''s; 401 `wrong-code`.'
operationId: post_totp_confirm_api_account_totp_confirm_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/RecoveryCodesOut'
/api/account/totp/disable:
post:
tags:
- Account
summary: Post Totp Disable
description: 'The second factor off, for a code from the app or a recovery code (the
phone may be what was lost), and the password where there is one. The
seed and every recovery code go with it.'
operationId: post_totp_disable_api_account_totp_disable_post
responses:
'204':
description: Successful Response
/api/account/totp/recovery:
post:
tags:
- Account
summary: Post Totp Recovery
description: 'Eight new recovery codes for a current code from the app — not for a
recovery code, which would let one leaked code mint eight more. Every
old one stops working.'
operationId: post_totp_recovery_api_account_totp_recovery_post
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/RecoveryCodesOut'
/api/account/sessions:
get:
tags:
- Account
summary: Get Sessions
description: 'The account''s live sessions, newest first, this browser''s flagged. Dates
and nothing else: no device, no address was ever kept to show.'
operationId: get_sessions_api_account_sessions_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
items:
$ref: '#/components/schemas/SessionOut'
type: array
title: Response Get Sessions Api Account Sessions Get
/api/account/sessions/revoke:
post:
tags:
- Account
summary: Post Sessions Revoke
description: 'End one of the account''s sessions. This browser''s own is a sign-out, so
its cookies go too. An id that is not the account''s — another''s, or one
already ended elsewhere — ends nothing and is answered the same, so the
list on another tab may be a little stale without an error.'
operationId: post_sessions_revoke_api_account_sessions_revoke_post
responses:
'204':
description: Successful Response
/api/account/unlink:
post:
tags:
- Account
summary: Post Unlink
description: 'Take a provider off the account. 409 `last-method` when it is the only
way in that would be left: no other provider, and no password with an
address to type it with. A provider not linked is nothing to do.'
operationId: post_unlink_api_account_unlink_post
responses:
'204':
description: Successful Response
/api/account/export:
post:
tags:
- Account
summary: Post Export
description: 'The account''s data as a JSON file to save (`ExportOut`), camelCase like
every answer here. A POST, as lib/api.ts asks for it: a per-user file is
not something a link, a prefetch or another site''s `` should fetch.
The wishlist is read from its tables directly, with each game''s slug
and title, and every alert that went out for it; the reviews likewise,
each with the slug and title of the game it is kept on; the email
switches exactly as the account page shows them (`_notifications`).'
operationId: post_export_api_account_export_post
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
/api/account/delete:
post:
tags:
- Account
summary: Post Delete
description: 'The account gone, and everything it owns with it: every table that
holds a row of the account''s references it `ON DELETE CASCADE`
(migration 0079), so one DELETE takes the sessions, the sign-ins, the
recovery codes, the wishlist and its alert history. `DELETE` typed to
confirm (422 `confirm-delete`), and the password where there is one.
This browser''s cookies are cleared in the answer.
The giveaway alerts and the weekly email are not the account''s (en.ts
`account.deleteLists`): each has its own unsubscribe link. News from
getcheapgames has none yet, and only this account could join it, so the
address leaves it with the account.'
operationId: post_delete_api_account_delete_post
responses:
'204':
description: Successful Response
/api/account/connections:
get:
tags:
- Account
summary: Get Connections
operationId: get_connections_api_account_connections_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ConnectionsOut'
/api/account/connections/epic/start:
get:
tags:
- Account
summary: Start Epic
description: Off to Epic to link (and connect) it, back on `//account?connected=epic`.
operationId: start_epic_api_account_connections_epic_start_get
parameters:
- name: cc
in: query
required: false
schema:
anyOf:
- type: string
- type: 'null'
title: Cc
responses:
'200':
description: Successful Response
content:
application/json:
schema: {}
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/api/account/connections/epic:
delete:
tags:
- Account
summary: Delete Epic
operationId: delete_epic_api_account_connections_epic_delete
responses:
'204':
description: Successful Response
/api/account/epic/friends:
get:
tags:
- Account
summary: Get Epic Friends
operationId: get_epic_friends_api_account_epic_friends_get
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/gcg__routers__connections__FriendsOut'
components:
schemas:
NotificationsOut:
properties:
email:
anyOf:
- type: string
- type: 'null'
title: Email
emailVerified:
type: boolean
title: Emailverified
items:
$ref: '#/components/schemas/NotificationItemsOut'
type: object
required:
- email
- emailVerified
- items
title: NotificationsOut
description: '`/api/account/notifications`: the switches, and the address they are about.'
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
NotificationItemsOut:
properties:
giveaways:
type: boolean
title: Giveaways
sales:
type: boolean
title: Sales
priceAlerts:
type: boolean
title: Pricealerts
news:
type: boolean
title: News
type: object
required:
- giveaways
- sales
- priceAlerts
- news
title: NotificationItemsOut
description: 'The account page''s email switches, each on or off, by the key `POST
/api/account/notifications` takes. The account and security emails are
not among them: those always go.'
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
input:
title: Input
ctx:
type: object
title: Context
type: object
required:
- loc
- msg
- type
title: ValidationError
MethodsOut:
properties:
password:
type: boolean
title: Password
google:
type: boolean
title: Google
steam:
type: boolean
title: Steam
epic:
type: boolean
title: Epic
type: object
required:
- password
- google
- steam
- epic
title: MethodsOut
description: The ways this account can sign in.
gcg__routers__connections__FriendsOut:
properties:
state:
type: string
enum:
- ok
- not_connected
- needs_permission
- error
title: State
friends:
items:
$ref: '#/components/schemas/gcg__routers__connections__FriendOut'
type: array
title: Friends
type: object
required:
- state
- friends
title: FriendsOut
TotpStartOut:
properties:
setup:
type: string
title: Setup
secret:
type: string
title: Secret
otpauth:
type: string
title: Otpauth
qr:
type: string
title: Qr
type: object
required:
- setup
- secret
- otpauth
- qr
title: TotpStartOut
description: 'An enrolment begun: what to scan or type, and the blob the confirm step hands back.'
ConnectionsOut:
properties:
epic:
$ref: '#/components/schemas/EpicConnectionOut'
type: object
required:
- epic
title: ConnectionsOut
gcg__routers__connections__FriendOut:
properties:
accountId:
type: string
title: Accountid
displayName:
type: string
title: Displayname
status:
type: string
enum:
- online
- away
- busy
- offline
- unknown
title: Status
game:
anyOf:
- type: string
- type: 'null'
title: Game
type: object
required:
- accountId
- displayName
- status
- game
title: FriendOut
MeOut:
properties:
id:
type: integer
title: Id
username:
type: string
title: Username
email:
anyOf:
- type: string
- type: 'null'
title: Email
emailVerified:
type: boolean
title: Emailverified
twoFactor:
type: boolean
title: Twofactor
methods:
$ref: '#/components/schemas/MethodsOut'
platforms:
items:
type: string
type: array
title: Platforms
createdAt:
type: string
format: date-time
title: Createdat
type: object
required:
- id
- username
- email
- emailVerified
- twoFactor
- methods
- platforms
- createdAt
title: MeOut
description: The signed-in account, as `/api/account/me` and every sign-in answer it.
EpicConnectionOut:
properties:
connected:
type: boolean
title: Connected
accountId:
anyOf:
- type: string
- type: 'null'
title: Accountid
displayName:
anyOf:
- type: string
- type: 'null'
title: Displayname
scopes:
items:
type: string
type: array
title: Scopes
friends:
type: boolean
title: Friends
presence:
type: boolean
title: Presence
type: object
required:
- connected
- accountId
- displayName
- scopes
- friends
- presence
title: EpicConnectionOut
SessionOut:
properties:
id:
type: integer
title: Id
createdAt:
type: string
format: date-time
title: Createdat
lastSeenAt:
type: string
format: date-time
title: Lastseenat
current:
type: boolean
title: Current
type: object
required:
- id
- createdAt
- lastSeenAt
- current
title: SessionOut
description: 'One signed-in browser: dates and whether it is this one. No device, no address.'
StatusOut:
properties:
status:
type: string
enum:
- ok
- sent
- expired
- invalid
- taken
- too-many
title: Status
type: object
required:
- status
title: StatusOut
description: 'One word: the email routes say `sent` whatever the address''s state.'
RecoveryCodesOut:
properties:
recoveryCodes:
items:
type: string
type: array
title: Recoverycodes
type: object
required:
- recoveryCodes
title: RecoveryCodesOut
description: Eight codes, `xxxxx-xxxxx`, shown once.