# Authentication

> For the complete documentation index, see [llms.txt](https://docs.banxa.com/llms.txt). Append `.md` to any page URL for its markdown version.


## Quick summary

- **Auth type:** HMAC-SHA256
- **Header:** `Authorization: Bearer API_KEY:SIGNATURE:NONCE`
- **Nonce:** Unix timestamp in microseconds (16 digits), unique per request
- **Signature:** Hex-encoded HMAC-SHA256 of a newline-separated canonical string


## Base URL

| Environment | Base URL |
|  --- | --- |
| Sandbox | `https://api.banxa-sandbox.com/eapi/v0/` |
| Production | `https://api.banxa.com/eapi/v0/` |


## Authorization header

Every request must include:

```
Authorization: Bearer API_KEY:SIGNATURE:NONCE
```

| Component | Description |
|  --- | --- |
| `API_KEY` | Public API key provided during onboarding |
| `SIGNATURE` | Hex-encoded HMAC-SHA256 signature |
| `NONCE` | Unix timestamp in microseconds (16 digits) |


## Building the signature

Construct a newline-separated canonical string, then sign it with HMAC-SHA256 using your API secret.

**GET request:**

```
METHOD\nPATH_WITH_QUERY_STRING\nNONCE
```

**POST request:**

```
METHOD\nPATH\nNONCE\nCOMPACT_JSON_BODY
```

Rules:

- Use the request **path only** — never the full URL with domain
- Include the query string in the path for GET requests
- JSON body must be compact — no whitespace between elements
- Generate a new nonce for every request


Nonce precision
Use **microseconds (16 digits)**. Seconds (10 digits) and milliseconds (13 digits) are also accepted, but millisecond precision causes nonce collisions under concurrent load: two requests generated in the same millisecond produce the same nonce, and the second is rejected as reused (`40003`).

Generate the nonce from a clock with genuine sub-millisecond resolution. Multiplying a millisecond timestamp by 1000 pads it to 16 digits without adding precision and does **not** prevent collisions.

Examples:

```
GET\n/eapi/v0/price\n1785804345837761

POST\n/eapi/v0/ramps\n1785804345837761\n{"identityReference":"example_01"}
```

## Code examples

```python Python
import hmac
import time

key = '[YOUR_API_KEY]'
secret = '[YOUR_API_SECRET]'

def generate_hmac(method, path, payload=None):
    nonce = str(int(time.time() * 1_000_000))
    parts = [method, path, nonce]
    if payload:
        parts.append(payload)
    data = '\n'.join(parts)
    signature = hmac.new(secret.encode('utf-8'), data.encode('utf-8'), 'sha256').hexdigest()
    return f'{key}:{signature}:{nonce}', nonce
```

```javascript Node.js
const crypto = require('crypto');

const key = '[YOUR_API_KEY]';
const secret = '[YOUR_API_SECRET]';

function generateHmac(method, path, payload = null) {
    const nonce = Math.round((performance.timeOrigin + performance.now()) * 1000).toString();
    const parts = [method, path, nonce];
    if (payload) parts.push(payload);
    const data = parts.join('\n');
    const signature = crypto.createHmac('sha256', secret).update(data).digest('hex');
    return `${key}:${signature}:${nonce}`;
}
```

```typescript TypeScript
import { createHmac } from 'node:crypto';

const key = '[YOUR_API_KEY]';
const secret = '[YOUR_API_SECRET]';

export function generateHmac(method: string, path: string, payload: string | null = null): string {
    const nonce = Math.round((performance.timeOrigin + performance.now()) * 1000).toString();
    const parts = [method, path, nonce];
    if (payload) parts.push(payload);
    const data = parts.join('\n');
    const signature = createHmac('sha256', secret).update(data).digest('hex');
    return `${key}:${signature}:${nonce}`;
}
```

```php PHP
<?php
$key = '[YOUR_API_KEY]';
$secret = '[YOUR_API_SECRET]';

function generateHmac($method, $path, $payload, $key, $secret) {
    $nonce = (string)(int)(microtime(true) * 1000000);
    $parts = [$method, $path, $nonce];
    if ($payload) $parts[] = $payload;
    $data = implode("\n", $parts);
    $signature = hash_hmac('sha256', $data, $secret);
    return "{$key}:{$signature}:{$nonce}";
}
```

```java Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.time.Instant;
import java.util.Formatter;

public class BanxaAuth {
    private static final String KEY = "[YOUR_API_KEY]";
    private static final String SECRET = "[YOUR_API_SECRET]";

    public String generateHmac(String method, String path, String payload) throws Exception {
        Instant now = Instant.now();
        String nonce = String.valueOf(now.getEpochSecond() * 1_000_000L + now.getNano() / 1_000L);
        String data = method + "\n" + path + "\n" + nonce;
        if (payload != null) data += "\n" + payload;

        SecretKeySpec signingKey = new SecretKeySpec(SECRET.getBytes(), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(signingKey);
        Formatter formatter = new Formatter();
        for (byte b : mac.doFinal(data.getBytes())) {
            formatter.format("%02x", b);
        }
        return KEY + ":" + formatter.toString() + ":" + nonce;
    }
}
```

```csharp .NET
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;

public static class BanxaAuth
{
    private const string Key = "[YOUR_API_KEY]";
    private const string Secret = "[YOUR_API_SECRET]";

    // DateTime.UtcNow advances in ~15 ms steps on Windows. Anchor once, then
    // advance with the high-resolution timer for genuine microsecond precision.
    private static readonly long AnchorMicros =
        DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() * 1_000L;
    private static readonly Stopwatch Clock = Stopwatch.StartNew();

    public static string GenerateHmac(string method, string path, string? payload = null)
    {
        // Elapsed.Ticks is always 100 ns units, so /10 is microseconds on every platform.
        var nonce = (AnchorMicros + Clock.Elapsed.Ticks / 10L).ToString();
        var parts = payload is null
            ? new[] { method, path, nonce }
            : new[] { method, path, nonce, payload };
        var data = string.Join("\n", parts);

        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(Secret));
        var signature = Convert.ToHexString(hmac.ComputeHash(Encoding.UTF8.GetBytes(data)))
                               .ToLowerInvariant();

        return $"{Key}:{signature}:{nonce}";
    }
}
```

```swift Swift
import CryptoKit

let key = "[YOUR_API_KEY]"
let secret = "[YOUR_API_SECRET]"

func generateHmac(method: String, path: String, payload: String? = nil) -> String {
    let nonce = String(Int(Date().timeIntervalSince1970 * 1_000_000))
    var parts = [method, path, nonce]
    if let payload = payload { parts.append(payload) }
    let data = parts.joined(separator: "\n")
    let secretKey = SymmetricKey(data: secret.data(using: .utf8)!)
    let signature = HMAC<SHA256>.authenticationCode(for: data.data(using: .utf8)!, using: secretKey)
        .map { String(format: "%02hhx", $0) }.joined()
    return "\(key):\(signature):\(nonce)"
}
```

```ruby Ruby
require 'openssl'

KEY = '[YOUR_API_KEY]'
SECRET = '[YOUR_API_SECRET]'

def generate_hmac(method, path, payload = nil)
    now = Time.now
    nonce = (now.to_i * 1_000_000 + now.usec).to_s
    parts = [method, path, nonce]
    parts << payload if payload
    data = parts.join("\n")
    signature = OpenSSL::HMAC.hexdigest('sha256', SECRET, data)
    "#{KEY}:#{signature}:#{nonce}"
end
```

## Authentication errors

| Code | Cause |
|  --- | --- |
| `40001` | Nonce is not a valid Unix timestamp — must be 10, 13, or 16 digits (seconds, milliseconds, or microseconds) |
| `40002` | Nonce is too old — check your system clock is in sync |
| `40003` | Nonce already used — generate a new nonce per request |
| `40100` | API key not recognised — check you are using the correct environment key |
| `40101` | Authorization header is malformed — format must be `Bearer API_KEY:SIGNATURE:NONCE` |
| `40102` | Authorization header is missing |
| `40103` | Signature mismatch — check path, newline separators, compact JSON, and correct secret |


## Best practices

- Generate a **new nonce for every request**
- Use **microsecond precision** for the nonce — millisecond timestamps collide under concurrent load
- Keep your system clock in sync (NTP)
- Always serialize JSON with **no whitespace** before signing
- Use the **request path only** — never the full URL with domain
- Log the `request_id` from error responses for debugging


If issues persist, contact your Banxa Account Manager.