API reference
Base URL and authentication
| Network | Base URL |
|---|---|
| Mainnet | https://mainnet-api.inodra.com |
| Testnet | https://testnet-api.inodra.com |
- Send the API key in the
x-api-keyheader. - The key needs the zkLogin scope. See API Key Scopes.
- The key's network selects the network. Devnet is not supported. A new organization's default project is on mainnet.
- Prove checks
maxEpochagainst the epoch of the key's network. Read the epoch from the same network. - Send JSON with
Content-Type: application/json. - The JWT audience must be on your client ID list in the dashboard. See The client ID rule.
Successful responses are wrapped in data. Errors are not.
POST /v1/zklogin/address
Returns the salt and the Sui address for a JWT. Cost: 2 credits.
curl -X POST https://mainnet-api.inodra.com/v1/zklogin/address \
-H "x-api-key: $INODRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "jwt": "eyJhbGciOiJSUzI1NiIs..." }'Request
| Field | Type | Required | Description |
|---|---|---|---|
jwt | string | Yes | The id_token from the OAuth provider. |
Inodra checks the JWT signature, the expiry, the issuer, and the audience.
Response 200
{
"data": {
"salt": "129390038577185583942388216820280642146",
"addressSeed": "13322897930163218532266430409510394316985274769125667290600321564259466511711",
"address": "0x7c5f0c1d2a3b4e5f60718293a4b5c6d7e8f901234567890abcdef1234567890ab",
"seedVersion": 1
}
}| Field | Type | Description |
|---|---|---|
salt | string | The user salt, as a decimal string. See Security. |
addressSeed | string | The zkLogin address seed, as a decimal string. |
address | string | The user's Sui address. |
seedVersion | number | The version of the salt derivation. The current version is 1. |
- The address is the current-scheme zkLogin address, not the legacy one.
- The address exists before any proof. The user can receive funds at once.
- The same JWT issuer, audience, and subject always give the same address in your organization.
POST /v1/zklogin/nonce
Starts a sign-in. It returns the nonce for the OAuth request, and the two values to keep for prove. Cost: 1 credit.
Use it in place of generateRandomness(), an epoch read, and generateNonce(). Inodra reads the epoch from the network of your API key, so maxEpoch always fits that network.
curl -X POST https://mainnet-api.inodra.com/v1/zklogin/nonce \
-H "x-api-key: $INODRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "extendedEphemeralPublicKey": "AO5Zf1y2wqP0Rk3mB0JtYx9o2fQ6l8d7s5h4g3f2e1dc" }'Request
| Field | Type | Required | Description |
|---|---|---|---|
extendedEphemeralPublicKey | string | Yes | Base64. The output of getExtendedEphemeralPublicKey(publicKey). |
epochs | number | No | Epochs after the current one in which the session stays valid. 0 to 30. Default 2. |
Response 200
{
"data": {
"nonce": "hTPpgF7XAKbW37rEUS6pEVZqmoI",
"jwtRandomness": "100681567828351849884072155819400689117",
"maxEpoch": 123,
"expiresAt": 1791417600000
}
}| Field | Type | Description |
|---|---|---|
nonce | string | Send it as the nonce parameter of the OAuth request. |
jwtRandomness | string | Keep it with the ephemeral key. Pass it to prove. |
maxEpoch | number | The last epoch in which the ephemeral key is valid. Pass it to prove. |
expiresAt | number | Unix time in milliseconds when maxEpoch is expected to end. An estimate. |
- An epoch is about 24 hours. The default gives a session of two to three days.
- The call does not store anything. Each call returns a new
jwtRandomness. - Mainnet and testnet only. Devnet returns
NETWORK_NOT_SUPPORTED.
POST /v1/zklogin/prove
Returns a zero-knowledge proof for one session. Cost: 50 credits. It takes about 3 seconds.
curl -X POST https://mainnet-api.inodra.com/v1/zklogin/prove \
-H "x-api-key: $INODRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jwt": "eyJhbGciOiJSUzI1NiIs...",
"extendedEphemeralPublicKey": "AO5Zf1y2wqP0Rk3mB0JtYx9o2fQ6l8d7s5h4g3f2e1dc",
"maxEpoch": 123,
"jwtRandomness": "100681567828351849884072155819400689117"
}'Request
| Field | Type | Required | Description |
|---|---|---|---|
jwt | string | Yes | The id_token from the OAuth provider. |
extendedEphemeralPublicKey | string | Yes | Base64. The output of getExtendedEphemeralPublicKey(publicKey). |
maxEpoch | number | Yes | The last epoch in which the ephemeral key is valid. |
jwtRandomness | string | Yes | Decimal string. The output of generateRandomness(). |
There is no salt in the request. Inodra derives it, so the proof always matches the address that Inodra issued.
Rules:
- The JWT
noncemust equalgenerateNonce(ephemeralPublicKey, maxEpoch, jwtRandomness). maxEpochmust be between the current epoch and the current epoch + 30.- Mainnet and testnet only. Devnet returns
NETWORK_NOT_SUPPORTED.
Response 200
{
"data": {
"proofPoints": {
"a": ["1716...", "2033...", "1"],
"b": [
["8247...", "1391..."],
["5024...", "1140..."],
["1", "0"]
],
"c": ["1289...", "7110...", "1"]
},
"issBase64Details": {
"value": "yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC",
"indexMod4": 1
},
"headerBase64": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFiYjc3NGJkIiwidHlwIjoiSldUIn0",
"addressSeed": "13322897930163218532266430409510394316985274769125667290600321564259466511711",
"maxEpoch": 123
}
}| Field | Type | Description |
|---|---|---|
proofPoints | object | The Groth16 proof points a, b, and c. |
issBase64Details | object | The position of the issuer in the JWT payload. |
headerBase64 | string | The JWT header, base64url. |
addressSeed | string | The zkLogin address seed, as a decimal string. |
maxEpoch | number | The same maxEpoch that you sent. |
These are the inputs and the maxEpoch that getZkLoginSignature needs:
import { getZkLoginSignature } from '@mysten/sui/zklogin'
const { maxEpoch, ...inputs } = proof
const signature = getZkLoginSignature({ inputs, maxEpoch, userSignature })One proof for each session
- Get one proof when the user logs in.
- Use it for every transaction until the current epoch passes
maxEpoch. - An epoch is about 24 hours. We recommend
maxEpoch = currentEpoch + 2.
Proof rate cap
zkLogin is available on paid plans. Each organization has a cap on proofs per minute:
| Plan | Proofs per minute |
|---|---|
| Team | 20 |
| Growth | 60 |
| Professional | 120 |
| Business | 300 |
| Enterprise | Unlimited |
Over the cap, the response is 429 PROOF_RATE_LIMITED with a Retry-After header in seconds.
Errors
Every error has this shape:
{
"error": "JWT audience is not in the allowed client IDs",
"code": "AUDIENCE_NOT_ALLOWED"
}Branch on code. error is the human-readable reason.
| Status | Code | Endpoint | Cause and fix |
|---|---|---|---|
400 | VALIDATION_FAILED | All | The request does not match the schema. Check the field names and types. |
400 | INVALID_JWT | Address, prove | Bad signature, expired, malformed, or an array audience. Get a new JWT. |
400 | UNSUPPORTED_ISSUER | Address, prove | The issuer is not one of the four exact issuers. Google must be https://accounts.google.com. |
400 | NETWORK_NOT_SUPPORTED | Nonce, prove | The key is a devnet key. Use mainnet or testnet. |
400 | INVALID_EPHEMERAL_KEY | Nonce, prove | Send the output of getExtendedEphemeralPublicKey(publicKey). |
400 | NONCE_MISMATCH | Prove | The JWT nonce does not match the key, maxEpoch, and randomness. Send the values from login. |
400 | MAX_EPOCH_OUT_OF_RANGE | Nonce, prove | Prove: set maxEpoch between the current epoch and the current epoch + 30. Nonce: set epochs from 0 to 30. |
401 | (no code) | All | The API key is missing or invalid. |
403 | INSUFFICIENT_SCOPE | All | Give the key the zkLogin scope. |
403 | PLAN_REQUIRED | All | The plan does not include zkLogin. Upgrade the organization's plan. |
403 | AUDIENCE_NOT_ALLOWED | Address, prove | Add the client ID on the dashboard zkLogin page. |
429 | CU_QUOTA_EXCEEDED | All | The plan's credit quota is used up. Upgrade or wait for the next period. |
429 | PROOF_RATE_LIMITED | Prove | The organization reached its proofs-per-minute cap. Wait for Retry-After. |
500 | INTERNAL_ERROR | All | An error on our side. Try again. Contact us if it continues. |
502 | PROVER_FAILED | Prove | The prover did not make a proof. Try again. |
404 | NOT_FOUND | All | zkLogin is switched off. Try again later. |
503 | PROVER_BUSY | Prove | All prover slots are in use. Wait for Retry-After, then try again with backoff. |
503 | SALT_UNAVAILABLE | Address, prove | The salt service cannot answer. Try again with backoff. |
503 | CHAIN_UNAVAILABLE | Nonce, prove | Inodra cannot read the current epoch. Try again with backoff. |
A key with allowed origins or rate limits can also return the codes in the API key error reference.
Retry-After
429 PROOF_RATE_LIMITED, 503 PROVER_BUSY, 503 SALT_UNAVAILABLE, and 503 CHAIN_UNAVAILABLE carry a Retry-After header in seconds. The same number is in the body as retryAfter.
If your backend sits between your app and Inodra, copy the status code, the code field, and the Retry-After header to your own response. A proxy drops headers unless you forward them, and the app then retries too early or not at all.
Billing
- Responses with status
5xxand429are not charged. - Nonce costs 1 credit. Address costs 2 credits. A successful prove costs 50 credits.
- A prove that fails with
4xxnever reaches the prover. It costs 2 credits, the same as address. - A request that fails schema validation, or that the plan does not allow, costs 1 credit.