Skip to content

Authentication

The ORD.NET API is wallet-authenticated. There are no API keys. You do not register an app or apply for credentials. The wallet signs a fresh challenge, the server verifies it, and you get a bearer token to use on subsequent requests.

  1. Ask the server for a challenge: POST /auth/challenge.
  2. Have the wallet sign every message in the challenge.
  3. Submit the signatures: POST /auth/verify.
  4. Use the returned sessionToken as a bearer token on subsequent requests.

Creates a new auth flow with one challenge per address that needs to sign.

Field Type Required Description
ordinalsAddress string yes Wallet’s ordinals address.
paymentAddress string yes Wallet’s payment address. This is the address checked for the funding requirement during /auth/verify.

Provider and wallet visibility are not request fields. When this flow links a new wallet binding to an existing signed-in profile, the new binding follows that profile’s existing public/private wallet visibility default.

{
"ordinalsAddress": "bc1p...",
"paymentAddress": "bc1q..."
}
Field Type Description
authRequestId string (UUID) Pass back to /auth/verify.
challenges array One entry per address that needs to sign. At least one.
challenges[].challengeId string (UUID) Pass back to /auth/verify.
challenges[].message string The exact text to sign.
challenges[].address string Wallet address that should sign this message.
challenges[].role string One of ordinals, payment.
{
"authRequestId": "11111111-1111-1111-1111-111111111111",
"challenges": [
{
"challengeId": "22222222-2222-2222-2222-222222222222",
"message": "Sign in to ord.net at 2026-05-08T18:00:00Z (nonce: ...)",
"address": "bc1p...",
"role": "ordinals"
},
{
"challengeId": "33333333-3333-3333-3333-333333333333",
"message": "Sign in to ord.net at 2026-05-08T18:00:00Z (nonce: ...)",
"address": "bc1q...",
"role": "payment"
}
]
}
  • 200: challenge created.
  • 400: malformed addresses or request body.
  • 401: signed-in browser session is stale.
  • 403: signed-in browser request came from an invalid origin.
  • 409: wallet is already linked or cannot be linked to this profile.
  • 429: rate limited.

See Errors and rate limits for response shapes.

Verifies one or more signed challenges and issues a session token on success.

Field Type Required Description
authRequestId string (UUID) yes The authRequestId from /auth/challenge.
verifications array yes (min 1, max 2) One entry per signed challenge.
verifications[].challengeId string (UUID) yes The challengeId from /auth/challenge.
verifications[].signature string yes Hex-encoded BIP-322 simple signature for message. Max 8192 characters.
verifications[].address string yes The address that produced the signature.
{
"authRequestId": "11111111-1111-1111-1111-111111111111",
"verifications": [
{
"challengeId": "22222222-2222-2222-2222-222222222222",
"signature": "<hex-signature>",
"address": "bc1p..."
},
{
"challengeId": "33333333-3333-3333-3333-333333333333",
"signature": "<hex-signature>",
"address": "bc1q..."
}
]
}
Field Type Description
profile object Your profile metadata.
profile.id string Stable profile id.
profile.username string | null Profile username.
profile.bio string | null Profile bio.
profile.avatarUrl string | null Profile avatar image URL.
profile.bannerUrl string | null Profile banner image URL.
walletBindings array Wallet bindings available to this API session.
walletBindings[].walletBindingId string (UUID) Send this as walletBindingId on writes.
walletBindings[].label string Display label for the binding.
walletBindings[].provider string Wallet provider recorded for the binding.
walletBindings[].ordinalsAddress string Ordinals address in the binding.
walletBindings[].paymentAddress string Payment address in the binding.
walletBindings[].isPublic boolean Whether this binding is public on the profile.
sessionToken string The bearer token.
expiresAt ISO 8601 datetime When the token stops being accepted. Tokens are currently valid for 1 hour.
{
"profile": {
"id": "44444444-4444-4444-4444-444444444444",
"username": "alice",
"bio": null,
"avatarUrl": null,
"bannerUrl": null
},
"walletBindings": [
{
"walletBindingId": "55555555-5555-5555-5555-555555555555",
"label": "Trading Wallet",
"provider": "unknown",
"ordinalsAddress": "bc1p...",
"paymentAddress": "bc1q...",
"isPublic": true
}
],
"sessionToken": "<bearer-token>",
"expiresAt": "2026-05-08T19:00:00.000Z"
}
  • 200: verification succeeded.
  • 400: missing or malformed fields.
  • 401: signature is invalid.
  • 403: verified payment address does not meet the funding requirement, or the wallet is otherwise not allowed.
  • 404: challenge was not found.
  • 409: challenge flow is invalid or wallet ownership changed before verification completed.
  • 410: challenge or flow expired. Start a fresh /auth/challenge.
  • 429: rate limited.
  • 503: funding check temporarily unavailable. Wait and retry with a fresh auth flow.

Send the token on every request:

Authorization: Bearer <sessionToken>
Content-Type: application/json

Choose one entry from walletBindings and send its walletBindingId on writes.

The token has these properties:

  • It is an API session token. Only /api/v1 bearer auth accepts it.
  • Web session cookies and social-login sessions are not accepted on this surface.
  • The token expires at expiresAt, currently 1 hour after issuance. Re-run the challenge and verify flow to refresh.
  • v1 tokens are not independently revocable or scoped per operation.

Returns the profile and wallet bindings without starting a new auth flow.

Field Type Description
profile object Your profile metadata.
walletBindings array Same shape as /auth/verify.
{
"profile": {
"id": "44444444-4444-4444-4444-444444444444",
"username": "alice",
"bio": null,
"avatarUrl": null,
"bannerUrl": null
},
"walletBindings": [
{
"walletBindingId": "55555555-5555-5555-5555-555555555555",
"label": "Trading Wallet",
"provider": "unknown",
"ordinalsAddress": "bc1p...",
"paymentAddress": "bc1q...",
"isPublic": true
}
]
}

To issue a sessionToken, /auth/verify checks the verified paymentAddress from the auth flow for at least 0.001 BTC (100,000 sats) confirmed. Pick the wallet address that should satisfy this requirement when you call /auth/challenge.

Status When
403 The verified payment address does not meet the funded threshold, or the wallet is otherwise not allowed.
503 The funding check is temporarily unavailable. Wait and retry with a fresh auth flow; do not work around the check.

The 503 body looks like this:

{
"error": "Trading API wallet eligibility is temporarily unavailable"
}

Missing token:

{ "error": "Bearer session token required" }

Invalid or expired token:

{ "error": "Invalid or expired bearer session token" }