v1 sunsets December 31, 2026. Migrate to v2 — see the migration guide.
Generating a JWT bearer token
Once an API key is created, a JWT bearer token can be generated by signing the key. The token is then used to authenticate requests to the APIs. To generate a JWT bearer token, sign an API key using theES256 algorithm, along with the name and privateKey from the downloaded API Key JSON file. The following snippets show how to create a JWT bearer token in various languages.
const { sign } = require("jsonwebtoken");
const crypto = require("crypto");
export const createAuthToken = () => {
const keyName = "org/{org-id}/apiKey/{api-key-id}"; // Replace with `name`
const keySecret = "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXX-----END PRIVATE KEY-----"; // Replace with the `privateKey`.
const serviceName = "developer-api";
const now = Math.floor(Date.now() / 1000)
const payload = {
aud: [serviceName],
iss: "openfx",
sub: keyName,
iat: now,
nbf: now,
exp: now + 120, // Max 2 minutes allowed
};
const options = {
header: {
alg: "ES256",
typ: "JWT",
kid: keyName,
nonce: crypto.randomBytes(16).toString("hex"),
},
};
const token = sign(payload, keySecret, options);
return token;
};
import { sign, type JwtHeader } from "jsonwebtoken";
import crypto from "node:crypto";
export const createAuthToken = (): string => {
const keyName = "org/{org-id}/apiKey/{api-key-id}"; // Replace with `name`
const keySecret = "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXX-----END PRIVATE KEY-----"; // Replace with the `privateKey`.
const serviceName = "developer-api";
const now = Math.floor(Date.now() / 1000);
const payload = {
aud: [serviceName],
iss: "openfx",
sub: keyName,
iat: now,
nbf: now,
exp: now + 120, // Max 2 minutes allowed
};
const options = {
header: {
alg: "ES256",
typ: "JWT",
kid: keyName,
nonce: crypto.randomBytes(16).toString("hex"),
} as JwtHeader,
};
const token = sign(payload, keySecret, options);
return token;
};
import time
import secrets
import jwt
def create_auth_token() -> str:
key_name = "org/{org-id}/apiKey/{api-key-id}" # Replace with `name`
key_secret = "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXX-----END PRIVATE KEY-----" # Replace with the `privateKey`.
service_name = "developer-api"
now = int(time.time())
payload = {
"aud": [service_name],
"iss": "openfx",
"sub": key_name,
"iat": now,
"nbf": now,
"exp": now + 120, # 2-minute TTL
}
headers = {
"kid": key_name,
"typ": "JWT",
"nonce": secrets.token_hex(16),
}
token = jwt.encode(payload, key_secret, algorithm="ES256", headers=headers)
return token
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class AuthTokenUtil {
private static final String KEY_NAME = "org/{org-id}/apiKey/{api-key-id}"; // Replace with `name`
private static final String KEY_SECRET = "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXX-----END PRIVATE KEY-----"; // Replace with the actual `privateKey`
private static final String SERVICE_NAME = "developer-api";
public static String createAuthToken() {
try {
long now = Instant.now().getEpochSecond();
// Generate random nonce
SecureRandom random = new SecureRandom();
byte[] nonceBytes = new byte[16];
random.nextBytes(nonceBytes);
String nonce = bytesToHex(nonceBytes);
// Parse the private key
PrivateKey privateKey = parsePrivateKey(KEY_SECRET);
// Create JWT with custom header (exactly matching TypeScript structure)
Map<String, Object> header = new HashMap<>();
header.put("kid", KEY_NAME);
header.put("nonce", nonce);
header.put("typ", "JWT");
// Note: alg is set via signWith() method, not in header map
String token = Jwts.builder()
.setHeader(header)
.claim("aud", Arrays.asList(SERVICE_NAME))
.setIssuer("openfx")
.setSubject(KEY_NAME)
.setNotBefore(new Date(now * 1000))
.setExpiration(new Date((now + 120) * 1000)) // Max 2 minutes allowed
.setIssuedAt(new Date(now * 1000))
.signWith(privateKey, SignatureAlgorithm.ES256)
.compact();
return token;
} catch (Exception e) {
throw new RuntimeException("Failed to create auth token", e);
}
}
private static PrivateKey parsePrivateKey(String privateKeyPem) throws Exception {
String privateKeyContent = privateKeyPem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
byte[] keyBytes = Base64.getDecoder().decode(privateKeyContent);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(keySpec);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
}
package utils
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// CreateAuthToken creates a JWT token with ES256 algorithm
func CreateAuthToken() (string, error) {
keyName := "org/{org-id}/apiKey/{api-key-id}" // Replace with `name`
keySecret := "-----BEGIN PRIVATE KEY-----XXXXXXXXXXXX-----END PRIVATE KEY-----" // Replace with the `privateKey`
serviceName := "developer-api"
now := time.Now().Unix()
// Parse the private key
privateKey, err := parsePrivateKey(keySecret)
if err != nil {
return "", fmt.Errorf("failed to parse private key: %w", err)
}
// Generate random nonce
nonceBytes := make([]byte, 16)
if _, err := rand.Read(nonceBytes); err != nil {
return "", fmt.Errorf("failed to generate nonce: %w", err)
}
nonce := hex.EncodeToString(nonceBytes)
// Create claims
claims := jwt.MapClaims{
"aud": []string{serviceName},
"iss": "openfx",
"sub": keyName,
"iat": now,
"nbf": now,
"exp": now + 120, // 2 minutes
}
// Create token with custom header
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
token.Header["kid"] = keyName
token.Header["nonce"] = nonce
token.Header["typ"] = "JWT"
// Sign the token
tokenString, err := token.SignedString(privateKey)
if err != nil {
return "", fmt.Errorf("failed to sign token: %w", err)
}
return tokenString, nil
}
// parsePrivateKey parses a PEM-encoded ECDSA private key
func parsePrivateKey(keyPEM string) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(keyPEM))
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the key")
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
// Try parsing as EC private key if PKCS8 fails
privateKey, err = x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
}
ecdsaKey, ok := privateKey.(*ecdsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("not an ECDSA private key")
}
return ecdsaKey, nil
}
Retrieve the organization ID from the
orgId parameter and the private key from the privateKey parameter in the JSON file downloaded during API key creation.Using the JWT bearer token
Once the JWT bearer token is generated, include it in theAuthorization header of each request. Example using cURL:
curl -L "https://api.openfx.com/v1/<required-route>" -H "Authorization: Bearer {JWT_Bearer_Token}"
{JWT_Bearer_Token} with the generated token.
- JWT bearer tokens expire 2 minutes after generation. A new token must be generated for continued API access.
- Setting a longer expiry time using
expis not allowed. - A unique JWT must be generated for each API request; reuse of tokens is not permitted.
Need idempotent state-changing requests? The
Idempotency-Key header is not supported on v1 — it is available on v2 and v3. See /v2/authentication and /v3/idempotency.