uloggdDevelopers
Developer portaluloggd
Get startedResourcesArcade
Developer portaluloggdIdentityCatalogProfileLibraryReviewsJournalListsAwardsScreenshotsSocialAccountNotificationsCommentsLikesHistoryPeopleMineralsArcade

Account

The account itself, and only with a session.

Nothing here takes a key. A key that could make another key would give itself every scope at once, and one that could ask for the export would be one leak away from the whole account. There is no scope that makes this safe, so there is none: session only.

A key that tries answers 403 forbidden.

GET/api/v1/account/username

Says whether a name is free, before anybody tries to take it.

No scope required · Counted against the read allowance

Query

FieldTypeNotes
qrequiredstringThe name to check. One that is not even shaped like a name answers available: false rather than erroring.
PUT/api/v1/account/username

Takes a name for the first time. Separate from the PATCH because they are different acts with different rules: an account without a name yet is finishing signing up, and one with a name is renaming.

No scope required · Counted against the write allowance

Body

FieldTypeNotes
usernamerequiredstring3 to 24 characters of a-z, 0-9 and _, starting and ending with neither _.
PATCH/api/v1/account/username

Renames. There is a waiting period between one change and the next, and the answer carries next_change_at to say when it ends.

No scope required · Counted against the write allowance

GET/api/v1/index

Recent public content for indexers. Up to 1000 items per kind: reviews, entries, lists and screenshots. RLS defines visibility.

Scope profile.read · Counted against the read allowance

POST/api/v1/account/bootstrap

Prepare the profile after sign-in. Session only. adopt_twitch=true adopts the existing Twitch identity.

No scope required · Counted against the write allowance

GET/api/v1/account/state

Active suspension, reason and infraction count for the caller, and infraction_history: every entry behind that count, newest first, with id, reason (the kind, stored in Portuguese: Aviso, Suspensão or Banimento permanente), details (moderation's own words, or null), expires_at and created_at. Session only.

No scope required · Counted against the read allowance

Example response

{
  "data": {
    "suspended": false,
    "state": null,
    "infractions": 1,
    "infraction_history": [
      {
        "id": "5c1d…",
        "reason": "Aviso",
        "details": "Spoilers sem aviso numa avaliação.",
        "expires_at": null,
        "created_at": "2026-09-14T18:02:11.204+00:00"
      }
    ],
    "username": "ada"
  }
}
GET/api/v1/account/birth-date

Returns the stored date of birth and how the age was confirmed. Session only: the columns are revoked from everyone, and the definer function answers for the caller and nobody else.

No scope required · Counted against the read allowance

PUT/api/v1/account/birth-date

Stores the date of birth. The age rule is the database's, which refuses rather than storing a date that is too recent.

No scope required · Counted against the write allowance

Body

FieldTypeNotes
birth_daterequiredstringA date as YYYY-MM-DD.
GET/api/v1/account/sessions

Where the account is signed in, and on what.

No scope required · Counted against the read allowance

DELETE/api/v1/account/sessions/{id}

Ends one session. Including the one making the request, which is how you sign out of everywhere at once.

No scope required · Counted against the write allowance

GET/api/v1/account/identities

The providers this account can sign in with.

No scope required · Counted against the read allowance

GET/api/v1/account/export

Everything the account has written, in one document. A session that still owes its second factor gets 403 with reason mfa_required.

No scope required · Counted against the read allowance

DELETE/api/v1/account/data

Throws away one kind of thing, by name. The category is required and never defaults: a delete that guesses what it was asked to remove is a delete nobody takes back. It asks, in the JSON body, for proof that the owner of the account is the one asking now (see password and code): a session left open used to be enough to empty the account. Before any proof, a session that still owes its second factor gets 403 forbidden with reason mfa_required. It answers with how many records went, not a flat "done": saying data is gone when there was none to remove is a claim rather than a confirmation.

No scope required · Counted against the write allowance

Query

FieldTypeNotes
categoryrequiredstringlibrary, reviews, sessions, journeys, lists, screenshots, comments, views or everything.

Body

FieldTypeNotes
passwordstringThe current password. Required, every time, for an account that has one. Without it, 403 forbidden with reason password_required; wrong, reason password_incorrect.
codestringFor an account with no password and two-factor on: six digits from the authenticator app or a recovery code, instead of signing in again. Without a password, a session started in the last 15 minutes is enough on its own; after that, 403 forbidden with reason reauthentication_required and second_factor saying whether a code would do. Tries at a password or code: 10 every 15 minutes per account, then 429 rate_limited with retry_after.
GET/api/v1/account/keys

The account's keys. Never the token: only its hash is stored, in a column no role that talks to this API is granted.

No scope required · Counted against the read allowance

POST/api/v1/account/keys

Creates a key on one of your apps, with the app's permissions. The token comes back once, in this answer, and never again: after that nobody can read it, not even us.

No scope required · Counted against the write allowance

Body

FieldTypeNotes
app_idrequireduuidThe key's app. Every key belongs to one, made in the developer portal.
namerequiredstringUp to 60 characters.
expires_in_daysintegerIn how many days it expires. null for never.
DELETE/api/v1/account/connections/{service}

Forgets a linked account: twitch or steam. Only the undoing lives here. Linking one is an OAuth round trip that has to come back to a page, so it starts and ends in the browser; forgetting is a single write, and writes are here.

No scope required · Counted against the write allowance

DELETE/api/v1/account/keys/{id}

Revokes a key. It takes effect on the very next request.

No scope required · Counted against the write allowance

GET/api/v1/account/apps

The account's apps, most recently changed first, with how many live keys each has and when one was last used. arcade sums up, in one line, where the app's game stands on Arcade, or is null for an app without Arcade: slug, status, hidden_reason, notice (suspended, flagged, hidden or null), live_release_number, listing (approved, pending, draft and rejected), releases (draft, pending, ready and rejected_latest) and data_events (pending, held, which is part of pending, and failed). It is the same member in every app answer, and Arcade has the details at /api/v1/account/apps/{id}/arcade.

No scope required · Counted against the read allowance

Example response

{
  "data": [
    {
      "id": "...",
      "name": "Orbit Runner",
      "description": "",
      "icon_url": null,
      "scopes": [],
      "created_at": "2026-10-14T18:00:00.000Z",
      "updated_at": "2026-10-14T18:00:00.000Z",
      "active_keys": 0,
      "last_used_at": null,
      "arcade": {
        "slug": "orbit-runner",
        "status": "listed",
        "hidden_reason": null,
        "notice": null,
        "live_release_number": 3,
        "listing": {
          "approved": true,
          "pending": null,
          "draft": false,
          "rejected": false
        },
        "releases": {
          "draft": 1,
          "pending": 0,
          "ready": 0,
          "rejected_latest": false
        },
        "data_events": { "pending": 0, "held": 0, "failed": 0 }
      }
    }
  ]
}
POST/api/v1/account/apps

Creates an app, with no permissions. Up to 10 per account.

No scope required · Counted against the write allowance

Body

FieldTypeNotes
namerequiredstringUp to 60 characters.
descriptionstringUp to 400 characters.
GET/api/v1/account/apps/{id}

An app and its keys, the live ones first, with the list's arcade member (null without Arcade).

No scope required · Counted against the read allowance

PATCH/api/v1/account/apps/{id}

Changes the name, the description or the permissions. The permissions apply to every live key of the app too, from the very next request.

No scope required · Counted against the write allowance

Body

FieldTypeNotes
namestringUp to 60 characters.
descriptionstringUp to 400 characters.
scopesarrayThe app's permissions, in full: any left out are taken away.
DELETE/api/v1/account/apps/{id}

Deletes the app and every key it holds, which stop working on the very next request. An app with a game on Arcade cannot go until the game is removed from Arcade: 409 conflict with reason remove_from_arcade_first.

No scope required · Counted against the write allowance

GET/api/v1/account/apps/{id}/keys

An app's keys. Never the token.

No scope required · Counted against the read allowance

POST/api/v1/account/apps/{id}/keys

Creates a key for the app, with its permissions. The token comes back once, in this answer.

No scope required · Counted against the write allowance

Body

FieldTypeNotes
namerequiredstringUp to 60 characters.
expires_in_daysintegerIn how many days it expires, from 1 to 3650. null for never.
POST/api/v1/account/apps/{id}/icon

Replaces the icon: the image as multipart/form-data, in the image field (JPEG, PNG, WebP, GIF or AVIF, up to 15 MB). It is treated as an avatar: shrunk to fit 512x512 (the portal shows it cut square), at most 500 KB still or 4 MB and 15 seconds animated, and put through the same content check. Answers id and icon_url. A file of another type or over 15 MB, or an image the content check refuses, is 400 invalid_request; so is an image that could not be processed, and then with a reason: invalid_image (it could not be read), invalid_format (the content is not the type sent), too_many_frames (over 300 frames), too_many_pixels (over 40 million pixels, counting every frame), animation_too_long (over 15 seconds), unsupported_animation (an animated PNG or AVIF, or an animation without each frame's duration) or too_heavy (still over 500 KB, or 4 MB animated, after optimization). 404 when the app is not yours.

No scope required · Counted against the write allowance

DELETE/api/v1/account/apps/{id}/icon

Removes the icon; the app shows its initials again.

No scope required · Counted against the write allowance

Proving it is you

Clearing data (DELETE /api/v1/account/data) cannot be taken back, so a session alone is not enough for it: somebody who finds a laptop left signed in has a session. The request carries a JSON body with proof that the owner is the one asking now:

  • An account with a password sends it as password, every time.
  • An account without one (it signs in with Google, Discord, Twitch or a passkey) needs nothing more if its session started in the last 15 minutes. After that it signs in again, or, with two-factor on, sends code: six digits from the authenticator app or a recovery code.

A refusal is 403 forbidden with a reason to act on:

{
  "error": {
    "code": "forbidden",
    "message": "Sign in again to do this: the session has to have started in the last 15 minutes.",
    "reason": "reauthentication_required",
    "second_factor": true
  }
}

reason is one of password_required, password_incorrect, reauthentication_required (with second_factor, whether a code would do) and code_incorrect. Each account gets 10 tries at a password or a code every 15 minutes; past that the answer is 429 rate_limited with reason too_many_attempts and retry_after in seconds.

Before any of that, a session that still owes its second factor (the account has two-factor on and the code was not entered at sign-in) gets 403 forbidden with reason mfa_required, whatever the body carries. The way through is finishing the two-factor step of signing in; a code in the body does not stand in for it.

An old session cannot make itself new to get past this. For an account without a password, the sign-in endpoints that would hand it a fresh session, a second factor or a passkey (turning two-factor on or off, finishing its setup, reading the authenticator secret, making recovery codes, registering a passkey) refuse once the session is older than those 15 minutes, and the way through is the same: sign in again. They live under /api/auth, the site's sign-in service, and answer 403 in its shape:

{
  "code": "RECENT_SIGN_IN_REQUIRED",
  "message": "Sign in again to do this: the session has to have started in the last 15 minutes."
}

Setting a first password (POST /api/account/password, the site's route for it) follows the same rule, because a password is also a proof that outlives the session: past those 15 minutes the answer is 403 with { "error": "reauthentication_required", "second_factor": false }.

An account with a password is unaffected: its proof is the password, and no new session stands in for it.

Social

Who the owner is connected to.

Notifications

The inbox, already resolved.