Skip to content
Last updated

API Signature Authentication Guide

As an extra layer of security, the Fig Markets API supports HMAC-SHA256 signatures to verify that requests are coming from authorized clients and haven't been tampered with during transmission.

This guide explains how to authenticate your API requests using HMAC-SHA256 signatures.

Currently, all GET, POST, PUT and DELETE requests require signature authentication.

How It Works

  1. Request Preparation: Your client creates a request with method, URI, and body
  2. String Construction: A specific string format is created from your request
  3. Signature Generation: HMAC-SHA256 is used to sign the string with your client secret
  4. Header Addition: The signature and timestamp are added as HTTP headers
  5. Request Transmission: The signed request is sent to the API

Required Headers

Every authenticated request must include these headers:

HeaderDescriptionExample
X-FIG-SignatureHMAC-SHA256 signature of the requesta1b2c3d4e5f6...
X-FIG-TimestampUnix timestamp when request was created1703123456
AuthorizationJWT bearer tokenBearer eyJhbGciOiJSUzI1NiIs...

String to Sign Format

The signature is calculated from a string with this exact format:

<timestamp>\n<http-method>\n<request-uri>\n<request-body>

The timestamp used to generate the signature must be the same as the X-FIG-Timestamp header.

Examples

DELETE Request (Delete RFQ):

1703123456
DELETE
/rfq/12345

POST Request (Create RFQ):

1703123456
POST
/rfq
{"baseCurrency":"BTC","quoteCurrency":"USD","amount":1.5,"anonymous":false,"settlementCredentials":"DBT-main","legs":[{"direction":"buy","instrumentId":12345,"ratio":1}]}

GET Request (Get RFQ by ID):

1703123456
GET
/rfq/12345

Important Notes:

  • Use uppercase HTTP methods (GET, POST, PUT, DELETE)
  • Include the full URI path with query parameters
  • For GET requests, the body is empty (just two newlines)
  • For DELETE requests with path parameters, the body is empty (just two newlines)
  • For DELETE requests with request body, include the exact JSON body
  • For POST/PUT requests, include the exact JSON body
  • Maintain exact newline characters (\n)

Implementation Examples

JavaScript/Node.js

const crypto = require('crypto');

class FigAPIClient {
    constructor(baseUrl, clientSecret, jwtToken) {
        this.baseUrl = baseUrl;
        this.clientSecret = clientSecret;
        this.jwtToken = jwtToken;
    }

    // Generate signature for a request
    generateSignature(timestamp, method, uri, body = '') {
        const stringToSign = `${timestamp}\n${method}\n${uri}\n${body}`;
        const hmac = crypto.createHmac('sha256', this.clientSecret);
        hmac.update(stringToSign);
        return hmac.digest('hex');
    }

    // Make an authenticated request
    async makeRequest(method, endpoint, body = null) {
        const uri = endpoint;
        const bodyString = body ? JSON.stringify(body) : '';

        const signature = this.generateSignature(timestamp, method, uri, bodyString);
        const timestamp = Math.floor(Date.now() / 1000).toString();

        const headers = {
            'X-FIG-Signature': signature,
            'X-FIG-Timestamp': timestamp,
            'Authorization': `Bearer ${this.jwtToken}`,
            'Content-Type': 'application/json'
        };

        const response = await fetch(`${this.baseUrl}${endpoint}`, {
            method: method,
            headers: headers,
            body: bodyString || undefined
        });

        return response;
    }

    // Example methods
    async createRFQ(rfqData) {
        return this.makeRequest('POST', '/rfq', rfqData);
    }

    async deleteRFQ(rfqId) {
        return this.makeRequest('DELETE', `/rfq/${rfqId}`);
    }

    async deleteQuote(quoteData) {
        return this.makeRequest('DELETE', '/rfq/quote', quoteData);
    }
}

// Usage example
const client = new FigAPIClient(
    'https://api.figmarkets.com/v1',
    'your-client-secret',
    'your-jwt-token'
);

// Create RFQ request
const rfqData = {
    baseCurrency: 'BTC',
    quoteCurrency: 'USD',
    amount: 1.5,
    anonymous: false,
    settlementCredentials: 'DBT-main',
    legs: [
        {
            direction: 'buy',
            instrumentId: 12345,
            ratio: 1
        }
    ]
};

const rfq = await client.createRFQ(rfqData);

// Delete RFQ request
const deleteResponse = await client.deleteRFQ(12345);

// Delete Quote request
const quoteData = {
    rfqId: 12345,
    rfqQuoteId: 67890,
    label: 'MyQuote-123'
};

const quoteDeleteResponse = await client.deleteQuote(quoteData);

Python

import hmac
import hashlib
import time
import requests
import json

class FigAPIClient:
    def __init__(self, base_url, client_secret, jwt_token):
        self.base_url = base_url
        self.client_secret = client_secret.encode('utf-8')
        self.jwt_token = jwt_token

    def generate_signature(self, timestamp, method, uri, body=''):
        """Generate HMAC-SHA256 signature for the request"""
        string_to_sign = f"{timestamp}\n{method}\n{uri}\n{body}"
        signature = hmac.new(
            self.client_secret,
            string_to_sign.encode('utf-8'),
            hashlib.sha256
        ).hexdigest()
        return signature

    def make_request(self, method, endpoint, body=None):
        """Make an authenticated request to the API"""
        uri = endpoint
        body_string = json.dumps(body) if body else ''

        signature = self.generate_signature(timestamp, method, uri, body_string)
        timestamp = str(int(time.time()))

        headers = {
            'X-FIG-Signature': signature,
            'X-FIG-Timestamp': timestamp,
            'Authorization': f'Bearer {self.jwt_token}',
            'Content-Type': 'application/json'
        }

        url = f"{self.base_url}{endpoint}"

        if method.upper() == 'GET':
            response = requests.get(url, headers=headers)
        elif method.upper() == 'DELETE':
            if body:
                response = requests.delete(url, headers=headers, json=body)
            else:
                response = requests.delete(url, headers=headers)
        else:
            response = requests.post(url, headers=headers, json=body)

        return response

    def create_rfq(self, rfq_data):
        """Create a new RFQ"""
        return self.make_request('POST', '/rfq', rfq_data)

    def delete_rfq(self, rfq_id):
        """Delete an RFQ by ID"""
        return self.make_request('DELETE', f'/rfq/{rfq_id}')

    def delete_quote(self, quote_data):
        """Delete a quote"""
        return self.make_request('DELETE', '/rfq/quote', quote_data)

# Usage example
client = FigAPIClient(
    'https://api.figmarkets.com/v1',
    'your-client-secret',
    'your-jwt-token'
)

# Create RFQ request
rfq_data = {
    'baseCurrency': 'BTC',
    'quoteCurrency': 'USD',
    'amount': 1.5,
    'anonymous': False,
    'settlementCredentials': 'DBT-main',
    'legs': [
        {
            'direction': 'buy',
            'instrumentId': 12345,
            'ratio': 1
        }
    ]
}

response = client.create_rfq(rfq_data)
print(response.json())

# Delete RFQ request
delete_response = client.delete_rfq(12345)
print(delete_response.json())

# Delete Quote request
quote_data = {
    'rfqId': 12345,
    'rfqQuoteId': 67890,
    'label': 'MyQuote-123'
}

quote_delete_response = client.delete_quote(quote_data)
print(quote_delete_response.json())

Go

package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"
)

type FigAPIClient struct {
	BaseURL      string
	ClientSecret string
	JWTToken     string
	HTTPClient   *http.Client
}

func NewFigAPIClient(baseURL, clientSecret, jwtToken string) *FigAPIClient {
	return &FigAPIClient{
		BaseURL:      baseURL,
		ClientSecret: clientSecret,
		JWTToken:     jwtToken,
		HTTPClient:   &http.Client{},
	}
}

func (c *FigAPIClient) generateSignature(method, uri, body string) string {
	stringToSign := fmt.Sprintf("%s\n%s\n%s", method, uri, body)
	mac := hmac.New(sha256.New, []byte(c.ClientSecret))
	mac.Write([]byte(stringToSign))
	return hex.EncodeToString(mac.Sum(nil))
}

func (c *FigAPIClient) makeRequest(method, endpoint string, body interface{}) (*http.Response, error) {
	uri := endpoint
	bodyString := ""

	if body != nil {
		bodyBytes, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		bodyString = string(bodyBytes)
	}

	signature := c.generateSignature(method, uri, bodyString)
	timestamp := fmt.Sprintf("%d", time.Now().Unix())

	url := c.BaseURL + endpoint
	var req *http.Request
	var err error

	if method == "GET" {
		req, err = http.NewRequest(method, url, nil)
	} else if method == "DELETE" && body != nil {
		req, err = http.NewRequest(method, url, strings.NewReader(bodyString))
	} else if method == "DELETE" {
		req, err = http.NewRequest(method, url, nil)
	} else {
		req, err = http.NewRequest(method, url, strings.NewReader(bodyString))
	}

	if err != nil {
		return nil, err
	}

	req.Header.Set("X-FIG-Signature", signature)
	req.Header.Set("X-FIG-Timestamp", timestamp)
	req.Header.Set("Authorization", "Bearer "+c.JWTToken)
	req.Header.Set("Content-Type", "application/json")

	return c.HTTPClient.Do(req)
}

func (c *FigAPIClient) CreateRFQ(rfqData map[string]interface{}) (*http.Response, error) {
	return c.makeRequest("POST", "/rfq", rfqData)
}

func (c *FigAPIClient) DeleteRFQ(rfqId int) (*http.Response, error) {
	return c.makeRequest("DELETE", fmt.Sprintf("/rfq/%d", rfqId), nil)
}

func (c *FigAPIClient) DeleteQuote(quoteData map[string]interface{}) (*http.Response, error) {
	return c.makeRequest("DELETE", "/rfq/quote", quoteData)
}

// Usage example
func main() {
	client := NewFigAPIClient(
		"https://api.figmarkets.com/v1",
		"your-client-secret",
		"your-jwt-token",
	)

	// Create RFQ request
	rfqData := map[string]interface{}{
		"baseCurrency":        "BTC",
		"quoteCurrency":       "USD",
		"amount":              1.5,
		"anonymous":           false,
		"settlementCredentials": "DBT-main",
		"legs": []map[string]interface{}{
			{
				"direction":     "buy",
				"instrumentId":  12345,
				"ratio":         1,
			},
		},
	}

	resp, err := client.CreateRFQ(rfqData)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer resp.Body.Close()
	fmt.Printf("Create RFQ Status: %s\n", resp.Status)

	// Delete RFQ request
	resp, err = client.DeleteRFQ(12345)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer resp.Body.Close()
	fmt.Printf("Delete RFQ Status: %s\n", resp.Status)

	// Delete Quote request
	quoteData := map[string]interface{}{
		"rfqId":      12345,
		"rfqQuoteId": 67890,
		"label":      "MyQuote-123",
	}

	resp, err = client.DeleteQuote(quoteData)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	defer resp.Body.Close()
	fmt.Printf("Delete Quote Status: %s\n", resp.Status)
}