GeminiGemini
Demo environmentGet API key
  • Overview
  • Crypto Trading
  • Prediction Markets
  • Perpetuals
  • Stocks
  • API Reference
  • SDKs & Tools
Changelog
Gemini logoGemini logo

© 2026 Gemini Space Station, Inc.

Get started
    IntroductionDemo environment
Platform
    Platform overview
    Authentication
      API keysOAuth 2.0
    AccountsRoles & permissionsInstruments & symbolsClient order IDsRate limitsErrors
Build with Gemini
    Build an agent
Resources
Authentication

OAuth 2.0

Gemini supports OAuth 2.0 for user-authorized API access. Gemini implements the authorization code grant flow with refresh tokens (response_type=code).

To get started, create an OAuth application in API Settings. Enter your application name, description, logo, and requested scopes.

When you create an app you choose its client type, and that choice is permanent:

  • A confidential client receives a client_id and a client_secret. Use this for applications running on secure servers you control.
  • A public client receives a client_id only. Use this for single-page apps, mobile apps, and desktop clients. Public clients must use PKCE.

Your client_id identifies your app in every request. Confidential clients also send a client_secret in token POST requests. Public clients never send secrets and use PKCE instead. You cannot change a client type after creation.

Gemini reviews your registered application before activating it in production. You can register applications immediately in the Sandbox Environment for testing. Email trading@gemini.com with questions.

OAuth 2.0 uses short-lived access tokens (24-hour expiration) for API requests and non-expiring refresh tokens to generate new access tokens.

Authorization Code Grant Flow

In the authorization code flow, you redirect users to Gemini to grant permissions. Gemini returns an authorization code that your backend exchanges for access and refresh tokens.

Authorization Request

Redirect users to Gemini to authorize access to your application. Users log in and approve requested scopes.

GET https://exchange.gemini.com/auth

Example authorization request (confidential client):

GET https://exchange.gemini.com/auth?client_id=my_id&response_type=code&redirect_uri=www.example.com/redirect&state=82350325&scope=balances:read,orders:create

Example authorization request (public client — adds the PKCE code_challenge and code_challenge_method):

GET https://exchange.gemini.com/auth?client_id=my_id&response_type=code&redirect_uri=http://127.0.0.1:51234/callback&state=82350325&scope=balances:read,orders:create&code_challenge=5S_YsMh19iBDX5plIVTXdtF3iJCbJ388EEVd5CVlWxU&code_challenge_method=S256

URL Parameters

ParameterTypeDescription
client_idstringUnique id of your application. This is provided in your API settings
response_typestringThe literal string "code"
redirect_uristringThe URL users should be returned to when they authorize. Note, this URL must be included in your list of approved redirect_uris in your app registration
statestringA random string that will be returned to you in the response. Required (and must be non-empty) for public clients to protect against CSRF; strongly recommended for confidential clients.
scopestringA comma separated list of scopes corresponding to the access you're requesting for your application. Note, these scopes must be included in your list of scopes in your app registration
code_challengestringRequired for public clients. The PKCE code challenge: BASE64URL-no-padding(SHA-256(code_verifier)). Always 43 characters for the S256 method. See Public Clients and PKCE
code_challenge_methodstringRequired for public clients. The literal string "S256". plain is not accepted

Authorization Response

Example redirect_uri response after user login

https://www.example.com/redirect?code=90123465-86ee-44ef-b4e3-835cc89bc8a3&state=82350325

On successful authorization Gemini will redirect your user to the redirect_uri you supplied with additional parameters code and state. The parameter state should match the state you provided, otherwise you should not trust the response. code is a temporary code which you will then use to obtain access and refresh tokens.

Authorization Token Request

Once you have received a code you can exchange it for access and refresh tokens.

POST https://exchange.gemini.com/auth/token

ParameterTypeDescription
client_idstringUnique id of your application. This is provided in your API settings
client_secretstringSecret of your application, provided when you register a confidential client in API settings. Confidential clients only — public clients must not send this, and a request that includes it will fail
codestringThe code you received from the authorization request
redirect_uristringThis must match the redirect_uri provided in the authorization request
grant_typestringThe literal string "authorization_code"
code_verifierstringRequired for public clients. The original code_verifier you generated before the authorization request (43–128 characters from [A-Za-z0-9-._~]). Gemini hashes it and compares it to the code_challenge you sent

Example token request (confidential client — sends client_secret):

Code
{ "client_id": "my_id", "client_secret": "my_secret", "code": "90123465-86ee-44ef-b4e3-835cc89bc8a3", "redirect_uri": "www.example.com/redirect", "grant_type": "authorization_code" }

Example token request (public client — sends code_verifier, no client_secret):

Code
{ "client_id": "my_id", "code": "90123465-86ee-44ef-b4e3-835cc89bc8a3", "redirect_uri": "http://127.0.0.1:51234/callback", "grant_type": "authorization_code", "code_verifier": "M25iVXpKU3puUjFaYWg3T1NDTDQtcW1ROUY5YXlwalNoc0hhakx-fkdq" }

Authorization Token Response

FieldTypeDescription
access_tokenstringA short-lived token to use in API call authentication. Is valid until the expires_in time reaches 0
refresh_tokenstringA refresh token to be used to generate new access tokens
token_typestringThe literal string "bearer"
scopestringThe scopes the access token will have access to
expires_inintegerThe lifetime in seconds of the access token, as measured in seconds from the current time

Example Token Response

Code
{ "access_token": "d9af2411-3e85-41bb-89f4-cf53750f04df", "refresh_token": "215c5a89-6df7-457b-ba0b-70695da8c91f", "token_type": "bearer", "scope": "balances:read,orders:create", "expires_in": 86399 }

Public Clients and PKCE

A public client is an OAuth app that runs somewhere it cannot keep a secret — a native mobile app, a desktop app, or a single-page app. Because the code ships to the user's device or browser, anyone can read it, so there is no client_secret to protect the token exchange. Public clients close that gap with PKCE (Proof Key for Code Exchange, RFC 7636).

PKCE is required for every public client, and it only works with the authorization code flow. Your app generates a one-time secret before it starts, sends only a hash of that secret when it asks for an authorization code, and reveals the original secret when it exchanges the code for tokens. Gemini hashes what you reveal and checks it against the hash you sent earlier. An attacker who intercepts the authorization code cannot use it, because they never saw the original secret.

How PKCE works

  1. Generate a code_verifier. A high-entropy random string of 43–128 characters using only A-Z, a-z, 0-9, -, ., _, ~ (RFC 7636 §4.1).
  2. Derive the code_challenge. BASE64URL-no-padding(SHA-256(code_verifier)) — exactly 43 characters for S256.
  3. Send the challenge on the authorization request. On GET https://exchange.gemini.com/auth, include code_challenge, code_challenge_method=S256, and a non-empty state. S256 is the only method Gemini accepts; plain is rejected.
  4. Send the verifier on the token request. On POST https://exchange.gemini.com/auth/token, include the original code_verifier. Do not include a client_secret. Gemini hashes the verifier and compares it to the challenge from step 3.

Public clients must also send a non-empty state on the authorization request. For confidential clients state is strongly recommended; for public clients it is required, because there is no secret to anchor the request and state is your protection against CSRF. Compare the state in the response to the value you sent, and reject the response if they don't match.

Refresh works the same way for a public client as for a confidential one, with one difference: omit the client_secret. There is no code_verifier on a refresh request.

Redirect URIs for public clients. To support native and desktop apps (RFC 8252), a public client may register an http loopback redirect URI — http://localhost, http://127.0.0.1, or http://[::1] — and the port may vary at runtime, so an ephemeral port chosen by the OS will be accepted. Loopback redirect URIs must use http (not https) and must not include user info. Every other (non-loopback) redirect URI must match your registered URI exactly.

Generating a code_verifier and deriving the S256 code_challenge in Python:

Code
import hashlib import base64 import secrets # 1. High-entropy code_verifier (token_urlsafe uses the unreserved set; 64 bytes -> ~86 chars) code_verifier = secrets.token_urlsafe(64) # 2. code_challenge = BASE64URL-no-padding( SHA-256( code_verifier ) ) digest = hashlib.sha256(code_verifier.encode("ascii")).digest() code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") # Send code_challenge + code_challenge_method=S256 on the authorization request, # then send code_verifier on the token request.

Using Refresh Tokens

The access token you receive will be relatively short-lived (default 24 hours). Once an access token has expired you can use your refresh token to generate a new access token. Refresh tokens never expire, however they are one-time use only as your request for a new access token will also return a new refresh token.

Getting a new access token is similar to getting the initial access and refresh tokens with slightly different parameters

Refresh Token Request

POST https://exchange.gemini.com/auth/token

Example Token Request

Code
{ "client_id": "my_id", "client_secret": "my_secret", "refresh_token": "215c5a89-6df7-457b-ba0b-70695da8c91f", "grant_type":"refresh_token" }
ParameterTypeDescription
client_idstringUnique id of your application
client_secretstringSecret of your application. This is provided when you first register an app in API settings
refresh_tokenstringYour refresh token
grant_typestringThe literal string "refresh_token"

Refresh Token Response

The response is the same as the initial token response – it will contain a new access token to query APIs and a new refresh token for when the access token expires.

ParameterTypeDescription
access_tokenstringA short-lived token to use in API call authentication
refresh_tokenstringA refresh token to be used to generate new access tokens
token_typestringThe literal string "bearer"
scopestringThe scopes the access token will have access to
expires_inintegerThe lifetime in seconds of the access token, as measured in seconds from the current time

Example Token Response

Code
{ "access_token": "c5e9459d-dc6f-4567-bce4-050ec965f22e", "expires_in": 86399, "scope": "balances:read,orders:create", "refresh_token": "ce0f14af-74dd-4767-a4e7-286e98b944c1", "token_type": "bearer" }

Using Access Tokens

Once you have an access token you can use it to call any Gemini API.

Most of the examples for the private APIs in these docs make use of API keys and corresponding headers, to use an access token simply update your header:

HeaderDescription
AuthorizationThe literal string "Bearer " concatenated to your temporary access_token
X-GEMINI-PAYLOADThe base64-encoded JSON payload (the payloads on OAuth do not require a nonce)
Code
import requests import json import base64 url = "https://api.gemini.com/v1/mytrades" access_token = "Bearer d9af2411-3e85-41bb-89f4-cf53750f04df" payload = { "request": "/v1/mytrades", "symbol": "btcusd" } encoded_payload = json.dumps(payload).encode() b64 = base64.b64encode(encoded_payload) request_headers = { "Authorization": access_token, "X-GEMINI-PAYLOAD": b64 } response = requests.post(url, data=None, headers=request_headers, verify=False) my_trades = response.json()
Code
import requests import json import base64 url = "https://api.gemini.com/v1/orders/history" access_token = "Bearer d9af2411-3e85-41bb-89f4-cf53750f04df" payload = { "request": "/v1/orders/history", "symbol": "btcusd" } encoded_payload = json.dumps(payload).encode() b64 = base64.b64encode(encoded_payload) request_headers = { "Authorization": access_token, "X-GEMINI-PAYLOAD": b64 } response = requests.post(url, data=None, headers=request_headers, verify=False) my_orders = response.json()

OAuth Scopes

Gemini uses a role-based system for its API. All OAuth applications are limited to the scopes in the following chart:

EndpointURIScope
Get Deposit Addresses/v1/addresses/:networkaddresses:read, addresses:create
New Deposit Address/v1/deposit/:network/newAddressaddresses:create
List Approved Addresses/v1/approvedAddresses/account/:networkaddresses:read
Remove Approved Address/v1/approvedAddresses/:network/removeaddresses:create
Get Available Balances/v1/balancesbalances:read
Get Notional Balancesv1/notionalbalances/:currencybalances:read
Add A Bank/v1/payments/addbankbanks:create
Add A Bank CAD/v1/payments/addbank/cadbanks:create
View Payment Methods/v1/payments/methodsbanks:read, banks:create
New Clearing Order/v1/clearing/newclearing:create
Cancel Clearing Order/v1/clearing/cancelclearing:create
Confirm Clearing Order/v1/clearing/confirmclearing:create
Clearing Order Status/v1/clearing/statusclearing:read
Clearing Order List/v1/clearing/listclearing:read
Clearing Broker List/v1/clearing/broker/listclearing:read
Clearing Trades/v1/clearing/tradesclearing:read
Withdraw Crypto Funds/v2/withdraw/:network/:tickercrypto:send
List Past Trades/v1/mytradeshistory:read
Get Orders History/v1/orders/historyhistory:read
Get Notional Volume/v1/notionalvolumehistory:read
Get Trade Volume/v1/tradevolumehistory:read
Transfers/v2/transfershistory:read
Custody Account Fees/v1/custodyaccountfeeshistory:read
Create New Order/v1/order/neworders:create
Cancel Order/v1/order/cancelorders:create
Cancel All Session Orders/v1/order/cancel/sessionorders:create
Cancel All Active Orders/v1/order/cancel/allorders:create
Wrap Order/v1/wrap/:symbolorders:create
Get Instant Quote/v1/instant/quoteorders:create
Execute Instant Order/v1/instant/executeorders:create
Get Order Status/v1/order/statusorders:read
Get Active Orders/v1/ordersorders:read
Account Detail/v1/accountaccount:read
Get Terms Status/v1/prediction-markets/terms/statusorders:read
Accept Terms/v1/prediction-markets/terms/acceptorders:create
Place Prediction Market Order/v1/prediction-markets/orderorders:create
Place Prediction Market Batch Orders/v1/prediction-markets/order/batchorders:create
Cancel Prediction Market Order/v1/prediction-markets/order/cancelorders:create
Cancel Prediction Market Batch Orders/v1/prediction-markets/order/batch/cancelorders:create
Get Active Prediction Market Orders/v1/prediction-markets/orders/activeorders:read
Get Prediction Market Order History/v1/prediction-markets/orders/historyorders:read
Get Prediction Market Positions/v1/prediction-markets/positionsorders:read
Prediction Market WebSocket Position Updatespositions@account, positions@account@1spositions:read or predictions:positions:read
Get Settled Prediction Market Positions/v1/prediction-markets/positions/settledorders:read
Get Prediction Market Volume Metrics/v1/prediction-markets/metrics/volumeorders:read
List Maker Rebate Payouts/v1/prediction-markets/maker-rebate/payoutsorders:read
Get Maker Rebate Lifetime Summary/v1/prediction-markets/maker-rebate/summary/totalorders:read
Get Liquidity Rewards Daily Summary/v1/prediction-markets/liquidity-rewards/summary/dailyorders:read
Get Liquidity Rewards Lifetime Summary/v1/prediction-markets/liquidity-rewards/summary/totalorders:read

API keys use different roles for to access Gemini APIs. Please see roles for descriptions of each role and scope for API keys.

Revoke OAuth Token

Clients can programmatically revoke an active access token or refresh token using the OAuth 2.0 Token Revocation endpoint (RFC 7009).

POST https://exchange.gemini.com/auth/token/revoke

Request Parameters

ParameterTypeDescription
tokenstringRequired. The access token or refresh token to revoke.
client_idstringRequired. Unique client ID of your application.
client_secretstringSecret of your application (Confidential clients only).

Example Token Revocation Request (Confidential Client):

Code
{ "client_id": "my_id", "client_secret": "my_secret", "token": "215c5a89-6df7-457b-ba0b-70695da8c91f" }

Response

On success, Gemini returns HTTP 200 OK with an empty response body. In accordance with RFC 7009 §2.2, HTTP 200 OK is returned regardless of whether the token was currently active or previously revoked.


Token Introspection & Authorization Metadata

Gemini also provides standard OAuth 2.0 endpoints for token introspection and server metadata discovery:

  • Token Introspection (RFC 7662): POST https://exchange.gemini.com/auth/token/introspect Pass client_id and token to inspect whether a token is active, its scopes, and its expiration timestamp.

  • Authorization Server Metadata (RFC 8414): GET https://exchange.gemini.com/.well-known/oauth-authorization-server Returns supported grant types (authorization_code, refresh_token), response types (code), PKCE challenge methods (S256), and endpoint URIs.


Revision History

DateNotes
2020/08/20Initial OAuth documentation
2026/08/03Updated OAuth 2.0 verification and added PKCE, token revocation, introspection, and server metadata specifications
API keysAccounts
On this page
  • Authorization Code Grant Flow
    • Authorization Request
    • URL Parameters
  • Authorization Response
    • Authorization Token Request
    • Authorization Token Response
  • Public Clients and PKCE
    • How PKCE works
  • Using Refresh Tokens
    • Refresh Token Request
    • Refresh Token Response
  • Using Access Tokens
  • OAuth Scopes
  • Revoke OAuth Token
    • Request Parameters
    • Response
  • Token Introspection & Authorization Metadata
  • Revision History
JSON
JSON
JSON
JSON
JSON
JSON