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.
Authenticated APIs do not submit their payload as POSTed data, but instead put it in the X-GEMINI-PAYLOAD
header.
All of them will include the request name and the nonce associated with the request.
Header Value Content-Length 0
Content-Type text/plain
X-GEMINI-APIKEY Your Gemini API key X-GEMINI-PAYLOAD The base64-encoded JSON payload X-GEMINI-SIGNATURE hex(HMAC_SHA384(base64(payload), key=api_secret))
Cache-Control no-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"
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)
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);
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));
}
}
}
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)
}
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;
}
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);
?>
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
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)
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 ()) / 1 e 9
// 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)
}
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);
}
}
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);
}
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)
}
}
Last modified on October 8, 2025