Gemini Crypto Exchange Logo
Authentication

API Key

Private API invocation

Payload creation

Authentication

Gemini uses API keys to allow access to private APIs. You can obtain these by logging on and creating a key in Settings/API. This will give you both an "API Key" that will serve as your user name, and an "API Secret" that you will use to sign messages.

All requests must contain a nonce. When provisioning a session key, you have the option of selecting "Uses a time based nonce". If this option is selected, the nonce, which must be in seconds, has to be within +/- 30 seconds of Unix Epoch timestamp to be deemed valid.

If you do not choose the option of a time based nonce, then the nonce has to be a number that will never be repeated and must increase between requests. This is to prevent an attacker who has captured a previous request from simply replaying that request. We recommend using a timestamp at millisecond or higher precision. The nonce need only be increasing with respect to the session that the message is on.

Sessions

A single account may have multiple API keys provisioned. In this document, we'll refer to these as "sessions". All orders will be recorded with the session that created them. The nonce associated with a request needs to be increasing with respect to the session that the nonce is used on.

This allows multithreaded or distributed trading systems to place orders independently of each other, without needing to synchronize clocks to avoid race conditions.

In addition, some operations (such as Cancel All Session Orders) act on the orders associated with a specific session.

Require Heartbeat

When provisioning a session key you have the option of marking the session as "Requires Heartbeat". The intention here is to specify that if connectivity to the exchange is lost for any reason, then all outstanding orders on this session should be canceled.

If this option is selected for a session, then if the exchange does not receive a message for 30 seconds, then it will assume there has been an interruption in service, and cancel all outstanding orders. To maintain the session, the trading system should send a heartbeat message at a more frequent interval. We suggest at most 15 seconds between heartbeats.

The heartbeat message is provided for convenience when there is no trading activity. Any authenticated API call will reset the 30 second clock, even if explicit heartbeats are not sent.

This feature is often referred to as "Cancel on Disconnect" on connection-oriented exchange protocols.

Payload

The payload of the requests will be a JSON object, which will be described in the documentation below. Rather than being sent as the body of the POST request, it will be base-64 encoded and stored as a header in the request.

All of them will include the request name and the nonce associated with the request.

HeaderValue
Content-Length0
Content-Typetext/plain
X-GEMINI-APIKEYYour Gemini API key
X-GEMINI-PAYLOADThe base64-encoded JSON payload
X-GEMINI-SIGNATUREhex(HMAC_SHA384(base64(payload), key=api_secret))
Cache-Controlno-cache

Master API Key

A group that contains multiple accounts can provision a Master API key. Master API keys offer the convenience of invoking any account API on behalf of an account within that group. To invoke an API on behalf of an account, add that account's nickname as an account parameter to your request payload.

Master API keys are formatted with a prepending master-, while account level API keys are formatted with a prepending account-.

The account parameter may be used on any API that performs an action for or against a single account.

Generating payload and signature header examples

cURL

# Create a proper payload with a single nonce value
NONCE=$(date +%s.%N)
PAYLOAD=$(echo -n "{\"request\":\"/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z\",\"nonce\":$NONCE}" | base64 | tr -d '\n')
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha384 -hmac "GEMINI_API_SECRET" | cut -d ' ' -f2)

# Execute the command with the calculated values
curl -X GET "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z" \
  -H "Content-Type: text/plain" \
  -H "Content-Length: 0" \
  -H "X-GEMINI-APIKEY: GEMINI_API_KEY" \
  -H "X-GEMINI-PAYLOAD: $PAYLOAD" \
  -H "X-GEMINI-SIGNATURE: $SIGNATURE" \
  -H "Cache-Control: no-cache"
bash

Python

import json
import base64
import hmac
import hashlib
import time

url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z"
gemini_api_secret = "GEMINI_API_SECRET".encode()
gemini_api_key= "GEMINI_API_KEY"
payload_nonce = time.time()

# Extract path from URL
_, path = url.split(".com")

# Create payload object
payload =  {"request": path, "nonce": payload_nonce}

# Convert payload to JSON string
encoded_payload = json.dumps(payload).encode()

# Base64 encode the payload
b64 = base64.b64encode(encoded_payload)

# Create HMAC signature using SHA-384
signature = hmac.new(gemini_api_secret, b64, hashlib.sha384).hexdigest()

# Headers
request_headers = {
    'Content-Type': "text/plain",
    'Content-Length': "0",
    'X-GEMINI-APIKEY': gemini_api_key,
    'X-GEMINI-PAYLOAD': b64,
    'X-GEMINI-SIGNATURE': signature,
    'Cache-Control': "no-cache"
}

print(request_headers)
python

Javascript

const crypto = require('crypto');

const url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z";
const geminiAPISecret = Buffer.from("GEMINI_API_SECRET");
const geminiAPIKey = "GEMINI_API_KEY";
const payloadNonce = Date.now() / 1000;

// Extract path from URL
const [_, path] = url.split(".com");

// Create payload object
const payload = {
  request: path,
  nonce: payloadNonce
};

// Convert payload to JSON string
const encodedPayload = JSON.stringify(payload);

// Base64 encode the payload
const b64 = Buffer.from(encodedPayload).toString('base64');

// Create HMAC signature using SHA-384
const signature = crypto.createHmac('sha384', geminiAPISecret)
  .update(b64)
  .digest('hex');

// Headers
const requestHeaders = {
  'Content-Type': "text/plain",
  'Content-Length': "0",
  'X-GEMINI-APIKEY': geminiAPIKey,
  'X-GEMINI-PAYLOAD': b64,
  'X-GEMINI-SIGNATURE': signature,
  'Cache-Control': "no-cache"
};

console.log(requestHeaders);
javascript

C#

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;

class Program
{
    static void Main()
    {
        string url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z";
        byte[] gemini_api_secret = Encoding.UTF8.GetBytes("GEMINI_API_SECRET");
        string gemini_api_key = "GEMINI_API_KEY";
        double payload_nonce = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000.0;

        // Extract path from URL
        string path = url.Split(new[] { ".com" }, StringSplitOptions.None)[1];

        // Create payload object
        var payload = new Dictionary<string, object>
        {
            { "request", path },
            { "nonce", payload_nonce }
        };

        // Convert payload to JSON string
        string jsonPayload = JsonSerializer.Serialize(payload);
        byte[] encodedPayload = Encoding.UTF8.GetBytes(jsonPayload);

        // Base64 encode the payload
        string b64 = Convert.ToBase64String(encodedPayload);

        // Create HMAC signature using SHA-384
        using (var hmac = new HMACSHA384(gemini_api_secret))
        {
            byte[] signatureBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(b64));
            string signature = BitConverter.ToString(signatureBytes).Replace("-", "").ToLower();

            // Headers
            var request_headers = new Dictionary<string, string>
            {
                { "Content-Type", "text/plain" },
                { "Content-Length", "0" },
                { "X-GEMINI-APIKEY", gemini_api_key },
                { "X-GEMINI-PAYLOAD", b64 },
                { "X-GEMINI-SIGNATURE", signature },
                { "Cache-Control", "no-cache" }
            };

            Console.WriteLine(string.Join("\n", request_headers));
        }
    }
}
csharp

Kotlin

import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.nio.charset.StandardCharsets
import kotlinx.serialization.json.*

fun main() {
    val url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z"
    val gemini_api_secret = "GEMINI_API_SECRET".toByteArray(StandardCharsets.UTF_8)
    val gemini_api_key = "GEMINI_API_KEY"
    val payload_nonce = System.currentTimeMillis() / 1000.0

    // Extract path from URL
    val path = url.split(".com")[1]

    // Create payload object
    val payload = buildJsonObject {
        put("request", JsonPrimitive(path))
        put("nonce", JsonPrimitive(payload_nonce))
    }

    // Convert payload to JSON string
    val encodedPayload = payload.toString().toByteArray(StandardCharsets.UTF_8)

    // Base64 encode the payload
    val b64 = Base64.getEncoder().encodeToString(encodedPayload)

    // Create HMAC signature using SHA-384
    val mac = Mac.getInstance("HmacSHA384")
    val secretKeySpec = SecretKeySpec(gemini_api_secret, "HmacSHA384")
    mac.init(secretKeySpec)
    val signatureBytes = mac.doFinal(b64.toByteArray(StandardCharsets.UTF_8))
    val signature = signatureBytes.joinToString("") { "%02x".format(it) }

    // Headers
    val request_headers = mapOf(
        "Content-Type" to "text/plain",
        "Content-Length" to "0",
        "X-GEMINI-APIKEY" to gemini_api_key,
        "X-GEMINI-PAYLOAD" to b64,
        "X-GEMINI-SIGNATURE" to signature,
        "Cache-Control" to "no-cache"
    )

    println(request_headers)
}
kotlin

Objective-C

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonHMAC.h>

@interface NSData (HexString)
- (NSString *)hexString;
@end

@implementation NSData (HexString)
- (NSString *)hexString {
    const unsigned char *bytes = (const unsigned char *)[self bytes];
    NSMutableString *hex = [NSMutableString new];
    
    for (NSInteger i = 0; i < [self length]; i++) {
        [hex appendFormat:@"%02x", bytes[i]];
    }
    return hex;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *url = @"https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z";
        NSString *gemini_api_secret_str = @"GEMINI_API_SECRET";
        NSData *gemini_api_secret = [gemini_api_secret_str dataUsingEncoding:NSUTF8StringEncoding];
        NSString *gemini_api_key = @"GEMINI_API_KEY";
        NSTimeInterval payload_nonce = [[NSDate date] timeIntervalSince1970];
        
        // Extract path from URL
        NSArray *components = [url componentsSeparatedByString:@".com"];
        NSString *path = components[1];
        
        // Create payload object
        NSDictionary *payload = @{
            @"request": path,
            @"nonce": @(payload_nonce)
        };
        
        // Convert payload to JSON string
        NSError *error;
        NSData *encodedPayload = [NSJSONSerialization dataWithJSONObject:payload
                                                                 options:0
                                                                   error:&error];
        
        // Base64 encode the payload
        NSString *b64 = [encodedPayload base64EncodedStringWithOptions:0];
        
        // Create HMAC signature using SHA-384
        NSData *b64Data = [b64 dataUsingEncoding:NSUTF8StringEncoding];
        unsigned char hmac[CC_SHA384_DIGEST_LENGTH];
        
        CCHmac(kCCHmacAlgSHA384, 
               [gemini_api_secret bytes], 
               [gemini_api_secret length], 
               [b64Data bytes], 
               [b64Data length], 
               hmac);
        
        NSData *signatureData = [NSData dataWithBytes:hmac length:sizeof(hmac)];
        NSString *signature = [signatureData hexString];
        
        // Headers
        NSDictionary *request_headers = @{
            @"Content-Type": @"text/plain",
            @"Content-Length": @"0",
            @"X-GEMINI-APIKEY": gemini_api_key,
            @"X-GEMINI-PAYLOAD": b64,
            @"X-GEMINI-SIGNATURE": signature,
            @"Cache-Control": @"no-cache"
        };
        
        NSLog(@"%@", request_headers);
    }
    return 0;
}
objectivec

PHP

<?php
$url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z";
$gemini_api_secret = "GEMINI_API_SECRET";
$gemini_api_key = "GEMINI_API_KEY";
$payload_nonce = microtime(true);

// Extract path from URL
$parts = explode(".com", $url);
$path = $parts[1];

// Create payload object
$payload = [
    "request" => $path,
    "nonce" => $payload_nonce
];

// Convert payload to JSON string
$encoded_payload = json_encode($payload);

// Base64 encode the payload
$b64 = base64_encode($encoded_payload);

// Create HMAC signature using SHA-384
$signature = hash_hmac('sha384', $b64, $gemini_api_secret);

// Headers
$request_headers = [
    'Content-Type' => "text/plain",
    'Content-Length' => "0",
    'X-GEMINI-APIKEY' => $gemini_api_key,
    'X-GEMINI-PAYLOAD' => $b64,
    'X-GEMINI-SIGNATURE' => $signature,
    'Cache-Control' => "no-cache"
];

print_r($request_headers);
?>
php

Ruby

require 'json'
require 'base64'
require 'openssl'
require 'time'

url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z"
gemini_api_secret = "GEMINI_API_SECRET"
gemini_api_key = "GEMINI_API_KEY"
payload_nonce = Time.now.to_f

# Extract path from URL
_, path = url.split(".com")

# Create payload object
payload = {"request" => path, "nonce" => payload_nonce}

# Convert payload to JSON string
encoded_payload = JSON.generate(payload)

# Base64 encode the payload
b64 = Base64.strict_encode64(encoded_payload)

# Create HMAC signature using SHA-384
signature = OpenSSL::HMAC.hexdigest('sha384', gemini_api_secret, b64)

# Headers
request_headers = {
  'Content-Type' => "text/plain",
  'Content-Length' => "0",
  'X-GEMINI-APIKEY' => gemini_api_key,
  'X-GEMINI-PAYLOAD' => b64,
  'X-GEMINI-SIGNATURE' => signature,
  'Cache-Control' => "no-cache"
}

puts request_headers
ruby

Swift

import Foundation
import CommonCrypto

let url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z"
let gemini_api_secret = "GEMINI_API_SECRET".data(using: .utf8)!
let gemini_api_key = "GEMINI_API_KEY"
let payload_nonce = Date().timeIntervalSince1970

// Extract path from URL
let components = url.components(separatedBy: ".com")
let path = components[1]

// Create payload object
let payload: [String: Any] = ["request": path, "nonce": payload_nonce]

// Convert payload to JSON string
let jsonData = try! JSONSerialization.data(withJSONObject: payload, options: [])

// Base64 encode the payload
let b64 = jsonData.base64EncodedString()

// Create HMAC signature using SHA-384
let signature = b64.data(using: .utf8)!.withUnsafeBytes { b64Ptr in
    gemini_api_secret.withUnsafeBytes { secretPtr in
        var digest = [UInt8](repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH))
        CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA384), secretPtr.baseAddress!, gemini_api_secret.count, b64Ptr.baseAddress!, b64.count, &digest)
        return digest.map { String(format: "%02x", $0) }.joined()
    }
}

// Headers
let request_headers = [
    "Content-Type": "text/plain",
    "Content-Length": "0",
    "X-GEMINI-APIKEY": gemini_api_key,
    "X-GEMINI-PAYLOAD": b64,
    "X-GEMINI-SIGNATURE": signature,
    "Cache-Control": "no-cache"
]

print(request_headers)
swift

Go

package main

import (
	"crypto/hmac"
	"crypto/sha512"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
	"time"
)

func main() {
	url := "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z"
	gemini_api_secret := []byte("GEMINI_API_SECRET")
	gemini_api_key := "GEMINI_API_KEY"
	payload_nonce := float64(time.Now().UnixNano()) / 1e9

	// Extract path from URL
	parts := strings.Split(url, ".com")
	path := parts[1]

	// Create payload object
	payload := map[string]interface{}{
		"request": path,
		"nonce":   payload_nonce,
	}

	// Convert payload to JSON string
	jsonPayload, _ := json.Marshal(payload)

	// Base64 encode the payload
	b64 := base64.StdEncoding.EncodeToString(jsonPayload)

	// Create HMAC signature using SHA-384
	h := hmac.New(sha512.New384, gemini_api_secret)
	h.Write([]byte(b64))
	signature := hex.EncodeToString(h.Sum(nil))

	// Headers
	request_headers := map[string]string{
		"Content-Type":       "text/plain",
		"Content-Length":     "0",
		"X-GEMINI-APIKEY":    gemini_api_key,
		"X-GEMINI-PAYLOAD":   b64,
		"X-GEMINI-SIGNATURE": signature,
		"Cache-Control":      "no-cache",
	}

	fmt.Println(request_headers)
}
go

Java

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONObject;

public class GeminiAPI {
    public static void main(String[] args) throws Exception {
        String url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z";
        String gemini_api_secret = "GEMINI_API_SECRET";
        String gemini_api_key = "GEMINI_API_KEY";
        double payload_nonce = Instant.now().toEpochMilli() / 1000.0;

        // Extract path from URL
        String path = url.split("\\.com")[1];

        // Create payload object
        Map<String, Object> payload = new HashMap<>();
        payload.put("request", path);
        payload.put("nonce", payload_nonce);

        // Convert payload to JSON string
        JSONObject jsonPayload = new JSONObject(payload);
        String payloadString = jsonPayload.toString();
        byte[] encodedPayload = payloadString.getBytes(StandardCharsets.UTF_8);

        // Base64 encode the payload
        String b64 = Base64.getEncoder().encodeToString(encodedPayload);

        // Create HMAC signature using SHA-384
        Mac sha384HMAC = Mac.getInstance("HmacSHA384");
        SecretKeySpec secretKeySpec = new SecretKeySpec(
            gemini_api_secret.getBytes(StandardCharsets.UTF_8), "HmacSHA384");
        sha384HMAC.init(secretKeySpec);
        byte[] signatureBytes = sha384HMAC.doFinal(b64.getBytes(StandardCharsets.UTF_8));
        StringBuilder signature = new StringBuilder();
        for (byte b : signatureBytes) {
            signature.append(String.format("%02x", b));
        }

        // Headers
        Map<String, String> request_headers = new HashMap<>();
        request_headers.put("Content-Type", "text/plain");
        request_headers.put("Content-Length", "0");
        request_headers.put("X-GEMINI-APIKEY", gemini_api_key);
        request_headers.put("X-GEMINI-PAYLOAD", b64);
        request_headers.put("X-GEMINI-SIGNATURE", signature.toString());
        request_headers.put("Cache-Control", "no-cache");

        System.out.println(request_headers);
    }
}
java

Rust

use std::time::{SystemTime, UNIX_EPOCH};
use hmac::{Hmac, Mac};
use sha2::Sha384;
use base64::encode;
use serde_json::{json, to_string};
use std::collections::HashMap;

fn main() {
    let url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z";
    let gemini_api_secret = b"GEMINI_API_SECRET";
    let gemini_api_key = "GEMINI_API_KEY";
    let payload_nonce = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs_f64();

    // Extract path from URL
    let path = url.split(".com").nth(1).unwrap();

    // Create payload object
    let payload = json!({
        "request": path,
        "nonce": payload_nonce
    });

    // Convert payload to JSON string
    let encoded_payload = to_string(&payload).unwrap();
    
    // Base64 encode the payload
    let b64 = encode(encoded_payload.as_bytes());

    // Create HMAC signature using SHA-384
    type HmacSha384 = Hmac<Sha384>;
    let mut mac = HmacSha384::new_from_slice(gemini_api_secret).unwrap();
    mac.update(b64.as_bytes());
    let signature_bytes = mac.finalize().into_bytes();
    let signature = signature_bytes.iter()
        .map(|b| format!("{:02x}", b))
        .collect::<String>();

    // Headers
    let mut request_headers = HashMap::new();
    request_headers.insert("Content-Type", "text/plain");
    request_headers.insert("Content-Length", "0");
    request_headers.insert("X-GEMINI-APIKEY", gemini_api_key);
    request_headers.insert("X-GEMINI-PAYLOAD", &b64);
    request_headers.insert("X-GEMINI-SIGNATURE", &signature);
    request_headers.insert("Cache-Control", "no-cache");

    println!("{:?}", request_headers);
}
rust

Scala

import java.util.Base64
import java.nio.charset.StandardCharsets
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import scala.collection.mutable.Map
import play.api.libs.json._

object GeminiAPI {
  def main(args: Array[String]): Unit = {
    val url = "https://api.gemini.com/v2/fxrate/EURUSD/2025-04-16T23:07:27.189Z"
    val gemini_api_secret = "GEMINI_API_SECRET".getBytes(StandardCharsets.UTF_8)
    val gemini_api_key = "GEMINI_API_KEY"
    val payload_nonce = System.currentTimeMillis() / 1000.0

    // Extract path from URL
    val parts = url.split("\\.com")
    val path = parts(1)

    // Create payload object
    val payload = Json.obj(
      "request" -> path,
      "nonce" -> payload_nonce
    )

    // Convert payload to JSON string
    val encodedPayload = Json.stringify(payload).getBytes(StandardCharsets.UTF_8)

    // Base64 encode the payload
    val b64 = Base64.getEncoder.encodeToString(encodedPayload)

    // Create HMAC signature using SHA-384
    val mac = Mac.getInstance("HmacSHA384")
    val secretKeySpec = new SecretKeySpec(gemini_api_secret, "HmacSHA384")
    mac.init(secretKeySpec)
    val signatureBytes = mac.doFinal(b64.getBytes(StandardCharsets.UTF_8))
    val signature = signatureBytes.map("%02x".format(_)).mkString

    // Headers
    val request_headers = Map[String, String](
      "Content-Type" -> "text/plain",
      "Content-Length" -> "0",
      "X-GEMINI-APIKEY" -> gemini_api_key,
      "X-GEMINI-PAYLOAD" -> b64,
      "X-GEMINI-SIGNATURE" -> signature,
      "Cache-Control" -> "no-cache"
    )

    println(request_headers)
  }
}
scala