# Authentication

## Quick summary

- **Auth type:** HMAC-SHA256
- **Header:** `Authorization: Bearer API_KEY:SIGNATURE:NONCE`
- **Nonce:** Unix timestamp in milliseconds, 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 milliseconds |


## 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


Examples:


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

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

## Code examples


```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() * 1000))
    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
const crypto = require('crypto');

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

function generateHmac(method, path, payload = null) {
    const nonce = Date.now().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}`;
}
```


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

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


```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
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 {
        String nonce = String.valueOf(System.currentTimeMillis());
        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;
    }
}
```


```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 * 1000))
    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
require 'openssl'

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

def generate_hmac(method, path, payload = nil)
    nonce = (Time.now.to_f * 1000).to_i.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 in milliseconds |
| 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**
- 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.