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.
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"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_headersruby
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