Hushxima
Marketplace

Authentication

API keys for machines, sessions for people, and the rules that decide which of the two an endpoint will accept.

Every request carries a bearer token:

Authorization: Bearer <credential>

There are two kinds of credential, and the API tells them apart by their shape. Anything starting with mk_ is an organisation API key. Anything else is treated as a session JWT.

Organisation API keys

An API key is bound to the organisation rather than to a person. It is what you put in a backend integration.

Create one from POST /keys. The full key comes back exactly once, in the api_key field of the response. We store only its SHA-256 hash, so there is no way to show it to you again. What you keep afterwards is the key_prefix, the first twelve characters, which is enough to recognise a key in a list but not to use it.

POST /keys, GET /keys and DELETE /keys/{key_id} all require an organisation admin signed in as a user. An API key cannot mint or revoke another API key.

POST
/keys

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

curl -X POST "https://example.com/keys" \  -H "Content-Type: application/json" \  -d '{}'
{  "api_key": "string",  "id": "string",  "key_prefix": "string"}
GET
/keys

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Response Body

application/json

curl -X GET "https://example.com/keys"
[  {    "created_at": "string",    "id": "string",    "key_prefix": "string",    "label": "string",    "last_used_at": "string",    "status": "string"  }]
DELETE
/keys/{key_id}

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Path Parameters

key_id*String

Response Body

application/json

curl -X DELETE "https://example.com/keys/string"
{  "ok": true}

User sessions

People sign in with an email and a password and get a JWT back. Its lifetime is set by the deployment and returned alongside it as expires_in_hours.

Accounts are created by invitation only. An organisation admin sends the invitation, the recipient hits POST /auth/register with the token from their email, and comes out the other side already signed in.

POST
/auth/login

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

curl -X POST "https://example.com/auth/login" \  -H "Content-Type: application/json" \  -d '{    "email": "string",    "password": "string"  }'
{  "expires_in_hours": 0,  "token": "string",  "twofa": {    "challenge_id": "string",    "enrolment_required": true,    "method": "string",    "pending_token": "string",    "sent_to": "string"  },  "user": {    "created_at": "string",    "email": "string",    "id": "string",    "last_login_at": "string",    "name": "string",    "org_id": "string",    "role": "string",    "status": "string"  }}
POST
/auth/register

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

curl -X POST "https://example.com/auth/register" \  -H "Content-Type: application/json" \  -d '{    "password": "string",    "token": "string"  }'
{  "expires_in_hours": 0,  "token": "string",  "twofa": {    "challenge_id": "string",    "enrolment_required": true,    "method": "string",    "pending_token": "string",    "sent_to": "string"  },  "user": {    "created_at": "string",    "email": "string",    "id": "string",    "last_login_at": "string",    "name": "string",    "org_id": "string",    "role": "string",    "status": "string"  }}
GET
/auth/me

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Response Body

application/json

curl -X GET "https://example.com/auth/me"
{  "created_at": "string",  "email": "string",  "id": "string",  "last_login_at": "string",  "name": "string",  "org_id": "string",  "role": "string",  "status": "string"}

When login does not hand you a token

POST /auth/login normally answers with token set. It answers with token: null and a twofa object in two cases: the account has a second factor enrolled and now has to prove it, in which case twofa.challenge_id names the challenge to answer, or the account is required to have one and does not yet, in which case twofa.enrolment_required is true.

Either way you get a pending_token. It is a real signed token, but it is scoped to the 2FA endpoints and nothing else, so presenting it anywhere in the rest of the API is rejected. Send it as the bearer token to POST /auth/login/2fa with the code to finish signing in, or to the enrolment endpoints to set a factor up first. Confirming enrolment also returns a full session, so a first-time setup signs you in.

Second factors are mandatory for platform staff only. Your own organisation's users can enrol voluntarily and are otherwise left alone.

POST
/auth/login/2fa

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

curl -X POST "https://example.com/auth/login/2fa" \  -H "Content-Type: application/json" \  -d '{    "challenge_id": "string",    "code": "string"  }'
{  "expires_in_hours": 0,  "token": "string",  "twofa": {    "challenge_id": "string",    "enrolment_required": true,    "method": "string",    "pending_token": "string",    "sent_to": "string"  },  "user": {    "created_at": "string",    "email": "string",    "id": "string",    "last_login_at": "string",    "name": "string",    "org_id": "string",    "role": "string",    "status": "string"  }}
GET
/auth/2fa/status

Authorization

bearerAuth
AuthorizationBearer <token>

JWT from /auth/login (users) or an org API key (mk_...) for integrations.

In: header

Response Body

application/json

curl -X GET "https://example.com/auth/2fa/status"
{  "backup_codes_left": 0,  "enrolled": true,  "method": "string",  "required": true}
POST
/auth/2fa/setup/start

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

curl -X POST "https://example.com/auth/2fa/setup/start" \  -H "Content-Type: application/json" \  -d '{    "method": "string"  }'
{  "method": "string",  "otpauth_uri": "string",  "secret": "string",  "sent_to": "string"}
POST
/auth/2fa/setup/confirm

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

curl -X POST "https://example.com/auth/2fa/setup/confirm" \  -H "Content-Type: application/json" \  -d '{    "code": "string"  }'
{  "backup_codes": [    "string"  ],  "expires_in_hours": 0,  "token": "string",  "user": {    "created_at": "string",    "email": "string",    "id": "string",    "last_login_at": "string",    "name": "string",    "org_id": "string",    "role": "string",    "status": "string"  }}

Forgotten passwords

POST /auth/forgot-password always answers {"ok": true}, whether or not the address belongs to an account. It will not tell a stranger who has one. The email carries a token for POST /auth/reset-password. Passwords are eight characters minimum.

POST
/auth/forgot-password

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

curl -X POST "https://example.com/auth/forgot-password" \  -H "Content-Type: application/json" \  -d '{    "email": "string"  }'
{  "ok": true}
POST
/auth/reset-password

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

curl -X POST "https://example.com/auth/reset-password" \  -H "Content-Type: application/json" \  -d '{    "new_password": "string",    "token": "string"  }'
{  "ok": true}

Roles, and what an API key cannot do

Users are either org_admin or org_member. Admin gates a specific list of endpoints, all of them things that spend money, hand out credentials, or change who can do either.

EndpointWhy it is gated
PUT /configchanges your resale markup
POST /balance/withdrawmoves money out
GET/POST /keys, DELETE /keys/{key_id}manages API credentials
GET /users, POST /users/invite, PUT /users/{id}/statusmanages who has access
GET /invitations, DELETE /invitations/{id}manages pending access

An API key has no user behind it, so it never satisfies an admin check. Calling one of the endpoints above with an API key returns:

{
  "error": {
    "code": "forbidden",
    "message": "org_admin role required (use a user session, not an API key)"
  }
}

Everything else works identically with either credential: catalog, quotes, purchases, wallets, key delivery, funding, balance reads and stats.

Rate limits and suspension

Requests are counted per organisation, per minute, against the limit on your org record. You can read it as rate_limit_rpm from GET /config. Going over returns 429 with code rate_limited. The counter is shared across every credential your org holds, so ten API keys do not buy ten times the quota.

A suspended organisation gets 403 forbidden on everything, with the message org suspended.

Last updated on

On this page