Skip to content

API reference ​

Base URL and authentication ​

NetworkBase URL
Mainnethttps://mainnet-api.inodra.com
Testnethttps://testnet-api.inodra.com
  • Send the API key in the x-api-key header.
  • 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 maxEpoch against 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.

bash
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 ​

FieldTypeRequiredDescription
jwtstringYesThe id_token from the OAuth provider.

Inodra checks the JWT signature, the expiry, the issuer, and the audience.

Response 200 ​

json
{
  "data": {
    "salt": "129390038577185583942388216820280642146",
    "addressSeed": "13322897930163218532266430409510394316985274769125667290600321564259466511711",
    "address": "0x7c5f0c1d2a3b4e5f60718293a4b5c6d7e8f901234567890abcdef1234567890ab",
    "seedVersion": 1
  }
}
FieldTypeDescription
saltstringThe user salt, as a decimal string. See Security.
addressSeedstringThe zkLogin address seed, as a decimal string.
addressstringThe user's Sui address.
seedVersionnumberThe 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.

bash
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 ​

FieldTypeRequiredDescription
extendedEphemeralPublicKeystringYesBase64. The output of getExtendedEphemeralPublicKey(publicKey).
epochsnumberNoEpochs after the current one in which the session stays valid. 0 to 30. Default 2.

Response 200 ​

json
{
  "data": {
    "nonce": "hTPpgF7XAKbW37rEUS6pEVZqmoI",
    "jwtRandomness": "100681567828351849884072155819400689117",
    "maxEpoch": 123,
    "expiresAt": 1791417600000
  }
}
FieldTypeDescription
noncestringSend it as the nonce parameter of the OAuth request.
jwtRandomnessstringKeep it with the ephemeral key. Pass it to prove.
maxEpochnumberThe last epoch in which the ephemeral key is valid. Pass it to prove.
expiresAtnumberUnix 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.

bash
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 ​

FieldTypeRequiredDescription
jwtstringYesThe id_token from the OAuth provider.
extendedEphemeralPublicKeystringYesBase64. The output of getExtendedEphemeralPublicKey(publicKey).
maxEpochnumberYesThe last epoch in which the ephemeral key is valid.
jwtRandomnessstringYesDecimal 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 nonce must equal generateNonce(ephemeralPublicKey, maxEpoch, jwtRandomness).
  • maxEpoch must be between the current epoch and the current epoch + 30.
  • Mainnet and testnet only. Devnet returns NETWORK_NOT_SUPPORTED.

Response 200 ​

json
{
  "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
  }
}
FieldTypeDescription
proofPointsobjectThe Groth16 proof points a, b, and c.
issBase64DetailsobjectThe position of the issuer in the JWT payload.
headerBase64stringThe JWT header, base64url.
addressSeedstringThe zkLogin address seed, as a decimal string.
maxEpochnumberThe same maxEpoch that you sent.

These are the inputs and the maxEpoch that getZkLoginSignature needs:

ts
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:

PlanProofs per minute
Team20
Growth60
Professional120
Business300
EnterpriseUnlimited

Over the cap, the response is 429 PROOF_RATE_LIMITED with a Retry-After header in seconds.

Errors ​

Every error has this shape:

json
{
  "error": "JWT audience is not in the allowed client IDs",
  "code": "AUDIENCE_NOT_ALLOWED"
}

Branch on code. error is the human-readable reason.

StatusCodeEndpointCause and fix
400VALIDATION_FAILEDAllThe request does not match the schema. Check the field names and types.
400INVALID_JWTAddress, proveBad signature, expired, malformed, or an array audience. Get a new JWT.
400UNSUPPORTED_ISSUERAddress, proveThe issuer is not one of the four exact issuers. Google must be https://accounts.google.com.
400NETWORK_NOT_SUPPORTEDNonce, proveThe key is a devnet key. Use mainnet or testnet.
400INVALID_EPHEMERAL_KEYNonce, proveSend the output of getExtendedEphemeralPublicKey(publicKey).
400NONCE_MISMATCHProveThe JWT nonce does not match the key, maxEpoch, and randomness. Send the values from login.
400MAX_EPOCH_OUT_OF_RANGENonce, proveProve: set maxEpoch between the current epoch and the current epoch + 30. Nonce: set epochs from 0 to 30.
401(no code)AllThe API key is missing or invalid.
403INSUFFICIENT_SCOPEAllGive the key the zkLogin scope.
403PLAN_REQUIREDAllThe plan does not include zkLogin. Upgrade the organization's plan.
403AUDIENCE_NOT_ALLOWEDAddress, proveAdd the client ID on the dashboard zkLogin page.
429CU_QUOTA_EXCEEDEDAllThe plan's credit quota is used up. Upgrade or wait for the next period.
429PROOF_RATE_LIMITEDProveThe organization reached its proofs-per-minute cap. Wait for Retry-After.
500INTERNAL_ERRORAllAn error on our side. Try again. Contact us if it continues.
502PROVER_FAILEDProveThe prover did not make a proof. Try again.
404NOT_FOUNDAllzkLogin is switched off. Try again later.
503PROVER_BUSYProveAll prover slots are in use. Wait for Retry-After, then try again with backoff.
503SALT_UNAVAILABLEAddress, proveThe salt service cannot answer. Try again with backoff.
503CHAIN_UNAVAILABLENonce, proveInodra 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 5xx and 429 are not charged.
  • Nonce costs 1 credit. Address costs 2 credits. A successful prove costs 50 credits.
  • A prove that fails with 4xx never 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.

The full-stack Sui data layer.