Skip to content
Hoody.com

The Hoody Authentication API covers account signup, sign-in (email/password, GitHub, Google, device flow), JWT-based session management, identity claims for third-party verification, and two-factor authentication (2FA). Use these endpoints to register new accounts, exchange credentials for access and refresh tokens, manage the current session, and protect accounts with TOTP-based 2FA.

All authenticated endpoints accept Authorization: Bearer <jwt>. After 2FA is enabled on an account, sensitive token-mutation endpoints additionally require a TOTP code.

Returns regions where free-tier servers exist, with boolean availability. Public, no auth required.

This endpoint takes no parameters.

Terminal window
curl -X GET https://api.hoody.icu/api/v1/auth/available-regions

Returns the public sign-in configuration (such as which identity providers are enabled) used to drive the OAuth flow.

This endpoint takes no parameters.

Terminal window
curl -X GET https://api.hoody.icu/api/v1/auth/config

Returns the ED25519 public key(s) used by Hoody to sign all API responses (X-Hoody-Signature header), identity claims issued at login, and container authorization claims.

This endpoint is intentionally public and requires no authentication.

This endpoint takes no parameters.

The data.signing_format object documents the wire formats used by Hoody’s signed artifacts. The fields you need most often are:

  • response_header: X-Hoody-Signature: t=<unix_ts>,kid=<key_id>,m=<method>,s=<status>,path=<request_url>,sig=<hex>
  • response_signed_data: ${t}\n${method}\n${status}\n${path}\n${responseBody} — the newline-joined tuple of timestamp, HTTP method, response status, request path, and response body. The body is the UTF-8 string of the JSON payload.
  • identity_claim_signed_data: base64url(JSON.stringify(claim_payload)) — the b64url string itself (UTF-8 bytes).
  • container_claim_signed_data: same as identity_claim_signed_data.
  • replay_tolerance_seconds: recommended maximum age (|now - t|) for response-signature freshness checks.
  1. Fetch this endpoint once (cache for 24h+).
  2. Locate the key by kid from the keys[] array.
  3. For response signatures: parse the X-Hoody-Signature header and verify sig against the newline-joined tuple ${t}\n${method}\n${status}\n${path}\n${responseBody}.
  4. For identity/container claims: verify claim.signature_hex against the UTF-8 bytes of claim.payload_b64.
  5. If kid in a signature/claim does not match any cached key, re-fetch this endpoint.
Terminal window
curl -X GET https://api.hoody.icu/api/v1/meta/public-key

An identity claim is a short-lived, ED25519-signed credential Hoody returns to a freshly authenticated user. It proves “Hoody authenticated this user” to systems outside Hoody — container programs, third-party services, downstream APIs — and is verified offline against the public key fetched from GET /api/v1/meta/public-key.

The claim is returned (when signing is configured on the server) by:

  • Password login — POST /api/v1/users/auth/login (see below)
  • 2FA completion — POST /api/v1/users/auth/2fa/verify (see below)
  • Email verification in tokens mode — POST /api/v1/auth/verify-email (see below)
  • Device flow terminal — POST /api/v1/auth/device/token
  • Hosted-auth exchange — POST /api/v1/auth/exchange

It is omitted when response_mode=intent is requested (the hosted-auth UI flow defers issuance to the PKCE exchange) and when HOODY_SIGNING_PRIVATE_KEY is not configured on the server instance.

FieldTypeDescription
kidstringKey identifier. Look up the matching key in keys[] from GET /api/v1/meta/public-key.
payload_b64stringbase64url-encoded JSON payload (the string itself is the signed input).
signature_hexstringED25519 detached signature over UTF-8 bytes of payload_b64 (128 hex chars = 64 bytes).
FieldTypeDescription
claim_typestringAlways "identity" for claims returned by Hoody auth flows.
issstringAlways "hoody-api".
substringThe authenticated user’s id.
usernamestringThe authenticated account’s username.
typestring"user" or "admin".
iatnumberUnix timestamp (seconds) when the claim was issued.
expnumberUnix timestamp (seconds) when the claim expires. Default lifetime is approximately 30 days for login-time claims.
kidstringThe signing key id used to issue this claim.
audstringAudience binding (only present on audience-bound re-issued claims). See strict two-way semantics in the verification checklist below.

Reject the claim if any of the following fail:

  1. Signature verifies against the UTF-8 bytes of payload_b64 (not the decoded JSON), using the public key whose kid matches payload.kid and bundle.kid.
  2. payload.claim_type === "identity".
  3. payload.iss === "hoody-api".
  4. payload.exp > now and payload.exp > payload.iat.
  5. payload.iat <= now + 300 (300-second clock skew tolerance).
  6. payload.kid === bundle.kid.
  7. If your flow uses audience binding, both directions must hold: the verifier’s audience matches payload.aud, and payload.aud matches the verifier’s identifier (strict two-way audience semantics).
import { createPublicKey, verify, createHash } from 'node:crypto';
const SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
function buildSpki(raw32) {
return Buffer.concat([SPKI_PREFIX, raw32]);
}
const kid = 'v1';
const hex = '8c8d683c125761bd9157e3a6f98c30d81cd7f2be4d16062a8342d1fcd2ca474a';
const rawKey = Buffer.from(hex, 'hex');
const pubKey = createPublicKey({ key: buildSpki(rawKey), format: 'der', type: 'spki' });
const claim = /* ...identity_claim from response... */;
const payloadBytes = Buffer.from(claim.payload_b64, 'utf8');
const sigBytes = Buffer.from(claim.signature_hex, 'hex');
if (!verify(null, payloadBytes, pubKey, sigBytes)) {
throw new Error('Identity claim signature invalid');
}
{
"kid": "v1",
"payload_b64": "eyJzdWIiOiJhMWIyYzNkNGU1ZjY3ODkwMTIzNDU2NzgiLCJ1c2VybmFtZSI6ImFsaWNlIiwidHlwZSI6InVzZXIiLCJpYXQiOjE3NDEyOTAwMDAsImV4cCI6MTc0Mzg4MjAwMCwia2lkIjoidjEifQ",
"signature_hex": "abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef1234abcdef12"
}

Decoded payload_b64 (base64url) gives:

{
"sub": "a1b2c3d4e5f6789012345678",
"username": "alice",
"type": "user",
"iat": 1741290000,
"exp": 1743882000,
"kid": "v1"
}

There are three patterns for letting a proxied app (one running inside a Hoody container, routed through the Hoody proxy) decide whether the inbound request carries a valid Hoody identity. Try them in this order:

  1. Native hoody-identity permission group. The built-in proxy permission group that exposes the parsed identity claim to proxied apps. See the hoody-identity permission group.

  2. App-level header. Read a non-reserved request header that the upstream caller (your gateway or the Hoody dashboard) sets to the claim bundle. The convention used by Hoody and downstream consumers is X-Hoody-Claim: <payload_b64>.<signature_hex> with a pinned kid.

  3. Proxy hook. A request-time hook in your app’s hoody.json that receives the headers (and claim bundle, if forwarded) and decides whether to allow the request. See the identity-claim-auth-gate hook recipe, which likewise reads from the non-reserved x-hoody-claim header.

For container-side, per-process claims (claims that prove a Hoody user authorized a specific command inside a single container, not the cross-app identity claim described here), see Identity claims on the Containers API.

GET endpoint the verification page navigates to. Verifies the provider is fully configured (302 back to the device page with ?error=provider_unavailable, ticket intact, when not), then consumes the device_verify_ticket + __Host-device_verify cookie atomically and redirects to the provider with a server-injected device_binding + attempt nonce. Sets Referrer-Policy: no-referrer.

NameInTypeRequiredDescription
ticketquerystringYes
providerquerystringYesOne of: github, google.
Terminal window
curl -X GET "https://api.hoody.icu/api/v1/auth/device/authorize?ticket=5b8e7d0c4b1f4a2c9d3a7e6f8b1c2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c&provider=github"

Redirects the browser to GitHub for OAuth authentication. Browser-only endpoint.

NameInTypeRequiredDescription
intentquerystringNoOAuth intent: login (default). star_check is accepted but ignored (retired).
redirect_uriquerystringYesFrontend URL to redirect to after OAuth completes (must be on an allowed domain).
code_challengequerystringYesPKCE code_challenge (base64url SHA-256 of code_verifier). Required — all OAuth flows must use PKCE post-migration.
invite_codequerystringNoOptional invite code (“coupon”) captured from the signup link. Normalized and hashed at redirect time — only the hash travels in the OAuth state, never the raw code. Memorized hash-only on a NEW account and applied automatically; not validated here.
Terminal window
curl -X GET "https://api.hoody.icu/api/v1/auth/github?intent=login&redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

Handles the GitHub OAuth callback. Browser-only endpoint.

NameInTypeRequiredDescription
codequerystringNo
statequerystringYes
errorquerystringNoProvider-side failure code (e.g. access_denied). Present instead of code when the user declines.
error_descriptionquerystringNo
error_uriquerystringNo
Terminal window
curl -X GET "https://api.hoody.icu/api/v1/auth/github/callback?code=ac123&state=550e8400-e29b-41d4-a716-446655440000"

Redirects the browser to Google for OAuth authentication. Browser-only endpoint.

NameInTypeRequiredDescription
redirect_uriquerystringYesFrontend URL to redirect to after OAuth completes (must be on an allowed domain).
code_challengequerystringYesPKCE code_challenge (base64url SHA-256 of code_verifier). Required — all OAuth flows must use PKCE post-migration.
invite_codequerystringNoOptional invite code (“coupon”) captured from the signup link. Normalized and hashed at redirect time — only the hash travels in the OAuth state, never the raw code. Memorized hash-only on a NEW account and applied automatically; not validated here.
Terminal window
curl -X GET "https://api.hoody.icu/api/v1/auth/google?redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

Handles the Google OAuth callback. Browser-only endpoint.

NameInTypeRequiredDescription
codequerystringNo
statequerystringYes
errorquerystringNoProvider-side failure code (e.g. access_denied). Present instead of code when the user declines.
error_descriptionquerystringNo
error_uriquerystringNo
Terminal window
curl -X GET "https://api.hoody.icu/api/v1/auth/google/callback?code=ac123&state=550e8400-e29b-41d4-a716-446655440000"

GET endpoint the popup navigates to. Consumes the launch ticket atomically and runs the existing OAuth redirect flow. Sets Referrer-Policy: no-referrer.

NameInTypeRequiredDescription
ticketquerystringYesOne-shot ticket from /launch/initiate response.
Terminal window
curl -X GET "https://api.hoody.icu/api/v1/auth/launch/start?ticket=5b8e7d0c4b1f4a2c9d3a7e6f8b1c2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c"

Registers a PKCE authorization request (code challenge + redirect URI) to begin the browser sign-in flow.

NameTypeRequiredDescription
code_challengestringYesPKCE challenge, 43 chars.
redirect_uristringYesMust start with https://.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/authorize \
-H "Content-Type: application/json" \
-d '{
"code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"redirect_uri": "https://app.example.com/auth/callback"
}'

Issues a device_code (polled by the CLI) and a short, hand-typeable user_code (shown to the human). Public, no auth. RFC-8628-inspired but deliberately not a standards-compliant device grant: no client_id/grant_type, lifecycle errors are nested under data, and the poll interval is a fixed 5s after slow_down.

NameTypeRequiredDescription
client_namestringNoShown on the verification page as “X is requesting access”.
code_challengestringNoOptional PKCE on the device flow itself; if present the poll REQUIRES the verifier.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/device/code \
-H "Content-Type: application/json" \
-d '{ "client_name": "Hoody CLI" }'

Page-only helper of the RFC-8628-inspired device flow (mirrors the RFC recommendation that the user can deny). Cookie + ticket gated, no credentials required — possession of the live ticket + cookie is the refusing authority. Flips the pending row to denied; the terminal poll then reports access_denied. status=pending-conditional: an approved row can never be un-approved.

NameTypeRequiredDescription
ticketstringYesdevice_verify_ticket from /device/verify_code.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/device/deny \
-H "Content-Type: application/json" \
-d '{ "ticket": "5b8e7d0c4b1f4a2c9d3a7e6f8b1c2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c" }'

Page-only helper. Verifies email/username + password with FULL login parity (shared per-account throttle, timing-normalized bcrypt) behind the device_verify_ticket + __Host-device_verify cookie gate. NEVER returns session tokens: no-2FA returns {status:"approved"} (tokens mint only at /device/token); 2FA returns {requires_2fa, temp_token} (device-bound partial, no code_challenge). Credential failures do NOT consume the ticket. Feature-flag off returns 404; schema-invalid body returns 422.

NameTypeRequiredDescription
ticketstringYesdevice_verify_ticket from /device/verify_code.
usernamestringNoUsername (alternative to email).
emailstringNoEmail address (alternative to username).
passwordstringYesAccount password (8-128 chars).
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/device/login \
-H "Content-Type: application/json" \
-d '{
"ticket": "5b8e7d0c4b1f4a2c9d3a7e6f8b1c2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
"username": "john_doe",
"password": "SecurePassword123!"
}'

Polled by the CLI while the user completes the browser step. Returns 400 + {data:{error}} for lifecycle states (authorization_pending | slow_down | access_denied | expired_token), 200 + token-set on approval (single-use; also requires the approving user’s session generation to still be current — a password reset / logout-all after approval yields expired_token), 429 on the outer flood guard. Public, no auth. Lifecycle errors are nested under data, unlike RFC 8628 §3.5.

NameTypeRequiredDescription
device_codestringYes64 hex chars.
code_verifierstringNoPKCE verifier, required when device flow was started with a challenge.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/device/token \
-H "Content-Type: application/json" \
-d '{ "device_code": "5b8e7d0c4b1f4a2c9d3a7e6f8b1c2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c" }'

Page-only helper. On a live pending row, mints a one-time device_verify_ticket and sets the __Host-device_verify cookie. Leaks only client_name + coarse status.

NameTypeRequiredDescription
user_codestringYesXXXX-XXXX user code (dashes optional).
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/device/verify_code \
-H "Content-Type: application/json" \
-d '{ "user_code": "PQRS-ABCD" }'

Completes the PKCE authorization-code flow by exchanging an authorization code and its code verifier for authentication tokens.

NameTypeRequiredDescription
codestringYes64 hex chars authorization code.
code_verifierstringYesPKCE verifier (43-128 chars).
redirect_uristringYesMust start with https://.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/exchange \
-H "Content-Type: application/json" \
-d '{
"code": "5b8e7d0c4b1f4a2c9d3a7e6f8b1c2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
"code_verifier": "qPsM3wSb1UvK8r2u4aD7nQ9Xz5tL3oF6iE0bN8sR2hP4yK1vW7cZ5jT9xA2dG",
"redirect_uri": "https://app.example.com/auth/callback"
}'

Send a password reset email. Always returns success to prevent email enumeration.

NameTypeRequiredDescription
emailstringYesEmail address associated with the account.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{ "email": "john.doe@example.com" }'

POST endpoint with Authorization: Bearer <intent or temp_token>. Idempotent. Used by the handoff page when the user dismisses the confirmation.

Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/intent/cancel \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

POST endpoint that issues a one-shot launch ticket bound to the request Origin header. The frontend navigates the popup to the returned launch_url, which consumes the ticket and runs the existing PKCE-protected OAuth flow with state_id + opener_origin plumbed through.

NameTypeRequiredDescription
providerstringYesOne of: github, google.
code_challengestringYesPKCE code_challenge (43 chars).
state_idstringYesUUID v4 per attempt.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/launch/initiate \
-H "Content-Type: application/json" \
-d '{
"provider": "github",
"code_challenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM",
"state_id": "550e8400-e29b-41d4-a716-446655440000"
}'

Resend the email verification link. Always returns success to prevent email enumeration.

NameTypeRequiredDescription
emailstringYesEmail address to resend verification to.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/resend-verification \
-H "Content-Type: application/json" \
-d '{ "email": "john.doe@example.com" }'

Set a new password using the reset token from the password reset email.

NameTypeRequiredDescription
tokenstringYesPassword reset token from the email link (64 chars).
passwordstringYesNew password (12-128 chars).
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "5b8e7d0c4b1f4a2c9d3a7e6f8b1c2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c",
"password": "NewSecurePassword456!"
}'

Create a new account with email and password. A verification email will be sent. Account is not active until email is verified.

NameTypeRequiredDescription
emailstringYesEmail address for the new account.
passwordstringYesPassword (12-128 chars, must include uppercase, lowercase, number, special char).
regionstringNoOptional preferred server region (e.g. eu-west). Auto-assigned by GeoIP if omitted.
invite_codestringNoOptional invite code (“coupon”). Memorized (hash-only) and applied automatically after email verification.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "new.user@example.com",
"password": "SecurePass123!",
"region": "eu-west"
}'

Verify the email address using the token from the verification email. Default response returns full login credentials. When response_mode=intent + code_challenge are provided, returns an opaque auth_intent_token for PKCE exchange (hosted auth UI flow). If 2FA is enabled on the account, returns requires_2fa + temp_token instead. On success, the login response includes an identity_claim (see Identity claims).

NameTypeRequiredDescription
tokenstringYesVerification token from the email link (64 chars).
response_modestringNointent or tokens.
code_challengestringNoPKCE code_challenge. Required when response_mode=intent.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/auth/verify-email \
-H "Content-Type: application/json" \
-d '{
"token": "5b8e7d0c4b1f4a2c9d3a7e6f8b1c2d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c"
}'

Retrieve the profile of the currently authenticated user. Works with JWT, auth token, or Basic authentication. When authenticated with an auth token, response includes data.auth_token introspection details (permissions and realm restrictions). This endpoint works even for banned users (read-only access). When authenticated with an auth token that lacks the resources.read_account permission, the response is reduced to identity fields (id, username, alias, public_key, timestamps); email and other account PII are omitted.

This endpoint takes no parameters.

Terminal window
curl -X GET https://api.hoody.icu/api/v1/users/auth/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Retrieve the profile of the currently authenticated user. Works with JWT, auth token, or Basic authentication. When authenticated with an auth token, response includes data.auth_token introspection details (permissions and realm restrictions). This endpoint works even for banned users (read-only access). When authenticated with an auth token that lacks the resources.read_account permission, the response is reduced to identity fields (id, username, alias, public_key, timestamps); email and other account PII are omitted.

This endpoint takes no parameters.

Terminal window
curl -X GET https://api.hoody.icu/api/v1/users/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Authenticate with username and password to receive a JWT access token (expires in 1 day) and a refresh token (expires in 7 days). Use the access token in the Authorization header for subsequent requests: Authorization: Bearer <token>. On success, the response also includes an identity_claim proving the login to third parties — see Identity claims.

NameTypeRequiredDescription
usernamestringNoUsername (alternative to email).
emailstringNoEmail address (alternative to username).
passwordstringYesAccount password (8-128 chars).
response_modestringNointent or tokens.
code_challengestringNoPKCE challenge (required when response_mode=intent).
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "john_doe",
"password": "SecurePassword123!"
}'

Log out the current user. Creates an audit log entry. In a stateless JWT setup, the client should discard the token. This endpoint works even for banned users.

Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/auth/logout \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Exchange a valid refresh token for a new access token and a new refresh token. Send the refresh token in the Authorization header as Authorization: Bearer <refreshToken>, or in the request body.

NameTypeRequiredDescription
refreshTokenstringYesValid refresh token from previous login or refresh.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/auth/refresh \
-H "Content-Type: application/json" \
-d '{ "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." }'

Mint a fresh, audience-bound identity claim for the authenticated caller without a re-login. First-party JWT sessions only — auth tokens, HTTP Basic, and impersonated sessions are rejected (403). The claim proves “Hoody authenticated this user” to the audience named in the request; third parties verify it offline against GET /api/v1/meta/public-key. Claim lifetime is clamped to [60s, min(server ceiling — default 24h, remaining JWT lifetime)], default 1 hour. When the underlying JWT has fewer than 60 seconds of validity remaining, the endpoint returns 400 REFRESH_REQUIRED. Dual rate limits apply (per-caller refresh budget and global issuance budget). See Identity claims.

NameTypeRequiredDescription
audiencestringYesConsumer identifier this claim is bound to (e.g. your app hostname). Printable ASCII without whitespace or double quotes. Verifiers reject the claim unless they expect exactly this audience.
expires_inintegerNoRequested claim lifetime in seconds. Clamped to [60, min(server ceiling, remaining JWT lifetime)]. Default: server-configured (1h).
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/auth/identity-claim \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"audience": "myapp.example.com",
"expires_in": 3600
}'

Check the current 2FA status for the authenticated user, including whether it is enabled and how many backup codes remain.

This endpoint takes no parameters.

Terminal window
curl -X GET https://api.hoody.icu/api/v1/users/auth/2fa/status \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Begin 2FA setup. Requires the current password for verification. Returns a QR code for the authenticator app and backup codes. Save the backup codes securely — they are shown only once.

NameTypeRequiredDescription
passwordstringYesCurrent account password for verification (8-128 chars).
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/auth/2fa/setup \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{ "password": "CorrectHorseBattery!9" }'

Verify and complete 2FA setup by providing the first code from your authenticator app. This confirms the setup is working correctly. On success, all sessions are revoked and a fresh token / refreshToken pair is returned (sessions_revoked: true). Adopt the new tokens to keep the current session alive.

NameTypeRequiredDescription
codestringYes6-digit code from authenticator app.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/auth/2fa/verify-setup \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{ "code": "123456" }'

Complete login by verifying a 2FA code. Use the temp_token from the login response and provide either a 6-digit OTP code or a backup code. On success, the response includes a fresh token / refreshToken pair AND an identity_claim proving the login to third parties — see Identity claims.

NameTypeRequiredDescription
temp_tokenstringNoTemporary token from login response (valid for 5 minutes). Alternatively pass it as Authorization: Bearer header.
codestringYes6-digit OTP code from authenticator app OR 10-character backup code.
response_modestringNointent or tokens.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/auth/2fa/verify \
-H "Content-Type: application/json" \
-d '{
"temp_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"code": "123456"
}'

POST /api/v1/users/auth/2fa/backup-codes/regenerate

Section titled “POST /api/v1/users/auth/2fa/backup-codes/regenerate”

Generate new backup codes (invalidates all existing ones). Requires password and current OTP code for security. Save the new codes securely — they are shown only once.

This endpoint takes no parameters.

NameTypeRequiredDescription
passwordstringYesCurrent account password (8-128 chars).
codestringYes6-digit OTP code from authenticator app.
Terminal window
curl -X POST https://api.hoody.icu/api/v1/users/auth/2fa/backup-codes/regenerate \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"password": "CorrectHorseBattery!9",
"code": "123456"
}'

Enable or disable the OTP requirement for token-mutation operations. Disabling requires both password and OTP (security downgrade requires primary-factor reauth).

This endpoint takes no parameters.

NameTypeRequiredDescription
enabledbooleanYestrue = require OTP for token mutations (default); false = skip OTP gate.
passwordstringNoRequired when setting enabled=false.
otp_codestringNoTOTP code or backup code. Required when setting enabled=false.
Terminal window
curl -X PUT https://api.hoody.icu/api/v1/users/auth/2fa/token-gate \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{ "enabled": true }'

Disable 2FA for the account. Requires both the current password and a valid OTP code (or backup code). On success, all sessions are revoked and a fresh token / refreshToken pair is returned (sessions_revoked: true). Adopt the new tokens to keep the current session alive.

This endpoint takes no parameters.

NameTypeRequiredDescription
passwordstringYesCurrent account password (8-128 chars).
codestringYes6-digit OTP code from authenticator app OR backup code.
Terminal window
curl -X DELETE https://api.hoody.icu/api/v1/users/auth/2fa \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"password": "CorrectHorseBattery!9",
"code": "123456"
}'