Skip to main content

Authentication — mint an access token

Every API call carries a short-lived JWT. You obtain it by signing a fresh payload with your private key and exchanging it at POST /auth-apps/access-token.

Signing scheme

The scheme must match exactly, or verification fails:

  • Payload: { publicKey, nonce, timestamp }
  • Canonicalize with the RFC 8785 JSON Canonicalization Scheme (json-canonicalize)
  • Sign the UTF-8 bytes with Ed25519 (nacl.sign.detached)
  • Encode the signature as base64
import * as nacl from 'tweetnacl';
import * as util from 'tweetnacl-util';
import { canonicalize } from 'json-canonicalize';
import { randomUUID } from 'crypto';

function buildAccessTokenRequest(secretKeyBase64, publicKeyBase64) {
const payload = {
publicKey: publicKeyBase64,
nonce: randomUUID(), // unique per request
timestamp: Math.floor(Date.now() / 1000), // UNIX seconds
};
const payloadCanonical = canonicalize(payload);
const signature = util.encodeBase64(
nacl.sign.detached(util.decodeUTF8(payloadCanonical), util.decodeBase64(secretKeyBase64)),
);
return { signature, payload, payloadCanonical };
}

Request

POST /auth-apps/access-token

{
"signature": "<base64 signature>",
"payload": {
"publicKey": "<base64>",
"nonce": "unique_nonce_12345",
"timestamp": 1625247600
},
"payloadCanonical": "{\"nonce\":\"unique_nonce_12345\",\"publicKey\":\"<base64>\",\"timestamp\":1625247600}"
}

:::tip Send payloadCanonical Include the exact canonical string you signed. Yumi verifies against it directly, removing any risk of a canonicalization mismatch between your library and ours. :::

Response

{ "success": true, "data": { "accessToken": "eyJhbGci...", "expires": 1625251200 } }

Rules

RuleFailure
timestamp must be recentSIGNATURE_EXPIRED
nonce must be unique per requestSIGNATURE_REUSED
Signature must verify against your registered public keyINVALID_APPLICATION_SIGNATURE

Cache the token until expires, then sign a new request. Send it on every call:

Authorization: Bearer <accessToken>

Next: Sending user data →