> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.finrock.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Finrock API Authentication: JWT Tokens and API Keys

> Finrock uses JWT tokens signed with RSA-4096 combined with an API key. Learn how to generate credentials and sign every request correctly.

Every authenticated request to the Finrock API requires two headers: a signed JWT token that proves you hold the private key registered against your account, and a static API key that identifies your API user. Neither header alone is sufficient — both must be present and valid for a request to succeed.

## Required Headers

Attach the following two headers to every authenticated API call:

| Header          | Format           | Description                                                                                            |
| --------------- | ---------------- | ------------------------------------------------------------------------------------------------------ |
| `Authorization` | `Bearer <JWT>`   | A base64-encoded JWT signed with your RSA-4096 private key using the RS256 algorithm                   |
| `x-api-key`     | `<your-api-key>` | The API key UUID generated from the Finrock control panel, e.g. `4466c45a-7b28-4c50-a0f7-198f8d7f34c5` |

```http theme={null}
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
x-api-key: 4466c45a-7b28-4c50-a0f7-198f8d7f34c5
```

## Step 1: Generate an RSA-4096 Key Pair

Finrock requires RSA keys with a minimum length of 4096 bits. Generate your key pair with OpenSSL:

```shell theme={null}
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out private_key.pem
openssl pkey -in private_key.pem -pubout -out public_key.pem
```

This produces two files:

* **`private_key.pem`** — your signing key; you keep this secret and use it to sign JWTs
* **`public_key.pem`** — the key you upload to the Finrock control panel when creating an API User

<Warning>
  Never share or commit your `private_key.pem`. Store it in a dedicated secrets manager such as AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault. Restrict read access to only the process or service that signs requests.
</Warning>

## Step 2: Register Your Public Key

Log in to the Finrock control panel and navigate to **API Users**. Create a new API User and upload the contents of `public_key.pem`. Finrock stores your public key and associates it with the API key UUID that is generated. Copy this UUID — you will use it as both the `x-api-key` header value and the `sub` claim in your JWT payload.

## Step 3: Build the JWT Payload

Each JWT you send must be freshly generated and scoped to the specific request it authenticates. The JWT payload must include all of the following fields:

| Field      | Type   | Description                                                           |
| ---------- | ------ | --------------------------------------------------------------------- |
| `uri`      | string | The URI path of the request, e.g. `/dac/v1/transactions`              |
| `nonce`    | string | A unique string; must be different for every request                  |
| `iat`      | number | Issued-at timestamp in seconds since Unix Epoch                       |
| `exp`      | number | Expiry timestamp in seconds since Epoch; must be less than `iat + 30` |
| `sub`      | string | Your API Key UUID                                                     |
| `bodyHash` | string | Hex-encoded SHA-256 hash of the raw HTTP request body                 |

<Warning>
  The `exp` field must be strictly less than `iat + 30` seconds. Any JWT with an expiry window of 30 seconds or more will be rejected. Keep the window short — 25 seconds is a safe default — to limit the replay window if a token is intercepted.
</Warning>

### Computing bodyHash

The `bodyHash` field is the lowercase hex-encoded SHA-256 hash of the raw request body string, exactly as it will be sent over the wire. For `GET` requests or requests with no body, hash an empty string.

**Example body:**

```json theme={null}
{"asset":"BTC","amount":0.0675,"type":"Withdraw","ts":1715933321398}
```

**Resulting SHA-256 bodyHash:**

```
6fd6a31980c78e97a5b1a12138b979934fb825f035c1ea2d3a8037a595bcedfb
```

<Note>
  Compute the hash on the exact byte string you send as the request body, before any encoding or transformation. Do not pretty-print or reorder JSON fields between computing the hash and sending the body.
</Note>

## Step 4: Sign the JWT

Sign the payload with your RSA private key using the **RS256** algorithm (RSASSA-PKCS1-v1\_5 with SHA-256). The signed JWT is a standard three-part base64url-encoded string. Pass it in the `Authorization` header as `Bearer <JWT>`.

## Code Examples

The following examples show how to build and sign the JWT in several languages. Full, runnable source files are available in the official snippet repository at [github.com/gofinrock/jwt-snippets](https://github.com/gofinrock/jwt-snippets).

<CodeGroup>
  ```javascript Node.js theme={null}
  // Full example: https://github.com/gofinrock/jwt-snippets/blob/main/node.js
  const fs = require("fs");
  const crypto = require("crypto");
  const jwt = require("jsonwebtoken"); // npm install jsonwebtoken

  const privateKey = fs.readFileSync("private_key.pem", "utf8");
  const apiKey = "4466c45a-7b28-4c50-a0f7-198f8d7f34c5";

  function buildAuthHeaders(uriPath, requestBody = "") {
    const now = Math.floor(Date.now() / 1000);

    const bodyHash = crypto
      .createHash("sha256")
      .update(requestBody, "utf8")
      .digest("hex");

    const payload = {
      uri: uriPath,
      nonce: crypto.randomUUID(),
      iat: now,
      exp: now + 25, // less than iat + 30
      sub: apiKey,
      bodyHash,
    };

    const token = jwt.sign(payload, privateKey, { algorithm: "RS256" });

    return {
      Authorization: `Bearer ${token}`,
      "x-api-key": apiKey,
      "Content-Type": "application/json",
    };
  }

  // Usage
  const body = JSON.stringify({ asset: "BTC" });
  const headers = buildAuthHeaders("/dac/v1/create-wallet", body);
  console.log(headers);
  ```

  ```python Python theme={null}
  # Full example: https://github.com/gofinrock/jwt-snippets/blob/main/python.py
  import hashlib
  import time
  import uuid
  import json

  import jwt  # pip install PyJWT
  from cryptography.hazmat.primitives import serialization

  with open("private_key.pem", "rb") as f:
      private_key = serialization.load_pem_private_key(f.read(), password=None)

  API_KEY = "4466c45a-7b28-4c50-a0f7-198f8d7f34c5"

  def build_auth_headers(uri_path: str, request_body: str = "") -> dict:
      now = int(time.time())

      body_hash = hashlib.sha256(request_body.encode("utf-8")).hexdigest()

      payload = {
          "uri": uri_path,
          "nonce": str(uuid.uuid4()),
          "iat": now,
          "exp": now + 25,  # must be less than iat + 30
          "sub": API_KEY,
          "bodyHash": body_hash,
      }

      token = jwt.encode(payload, private_key, algorithm="RS256")

      return {
          "Authorization": f"Bearer {token}",
          "x-api-key": API_KEY,
          "Content-Type": "application/json",
      }

  # Usage
  body = json.dumps({"asset": "BTC"})
  headers = build_auth_headers("/dac/v1/create-wallet", body)
  print(headers)
  ```

  ```csharp C# theme={null}
  // Full example: https://github.com/gofinrock/jwt-snippets/blob/main/csharp.cs
  using System;
  using System.Security.Cryptography;
  using System.Text;
  using System.Text.Json;
  using Microsoft.IdentityModel.Tokens;
  using System.IdentityModel.Tokens.Jwt;
  using System.Security.Claims;

  // Requires: dotnet add package System.IdentityModel.Tokens.Jwt
  //           dotnet add package Microsoft.IdentityModel.Tokens

  const string apiKey = "4466c45a-7b28-4c50-a0f7-198f8d7f34c5";
  string privateKeyPem = File.ReadAllText("private_key.pem");

  (string authHeader, string apiKeyHeader) BuildAuthHeaders(string uriPath, string requestBody = "")
  {
      long now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();

      byte[] bodyBytes = Encoding.UTF8.GetBytes(requestBody);
      byte[] hashBytes = SHA256.HashData(bodyBytes);
      string bodyHash = Convert.ToHexString(hashBytes).ToLower();

      using var rsa = RSA.Create();
      rsa.ImportFromPem(privateKeyPem);
      var rsaKey = new RsaSecurityKey(rsa);
      var credentials = new SigningCredentials(rsaKey, SecurityAlgorithms.RsaSha256);

      var claims = new[]
      {
          new Claim("uri", uriPath),
          new Claim("nonce", Guid.NewGuid().ToString()),
          new Claim("iat", now.ToString(), ClaimValueTypes.Integer64),
          new Claim("exp", (now + 25).ToString(), ClaimValueTypes.Integer64),
          new Claim("sub", apiKey),
          new Claim("bodyHash", bodyHash),
      };

      var token = new JwtSecurityToken(claims: claims, signingCredentials: credentials);
      string jwt = new JwtSecurityTokenHandler().WriteToken(token);

      return ($"Bearer {jwt}", apiKey);
  }

  // Usage
  string body = JsonSerializer.Serialize(new { asset = "BTC" });
  var (auth, key) = BuildAuthHeaders("/dac/v1/create-wallet", body);
  Console.WriteLine($"Authorization: {auth}");
  Console.WriteLine($"x-api-key: {key}");
  ```

  ```php PHP theme={null}
  <?php
  // Full example: https://github.com/gofinrock/jwt-snippets/blob/main/php-example.php
  // Requires: composer require firebase/php-jwt

  use Firebase\JWT\JWT;

  $privateKey = file_get_contents('private_key.pem');
  $apiKey = '4466c45a-7b28-4c50-a0f7-198f8d7f34c5';

  function buildAuthHeaders(string $uriPath, string $requestBody = ''): array
  {
      global $privateKey, $apiKey;

      $now = time();
      $bodyHash = hash('sha256', $requestBody);

      $payload = [
          'uri'      => $uriPath,
          'nonce'    => bin2hex(random_bytes(16)),
          'iat'      => $now,
          'exp'      => $now + 25, // must be less than iat + 30
          'sub'      => $apiKey,
          'bodyHash' => $bodyHash,
      ];

      $token = JWT::encode($payload, $privateKey, 'RS256');

      return [
          'Authorization' => 'Bearer ' . $token,
          'x-api-key'     => $apiKey,
          'Content-Type'  => 'application/json',
      ];
  }

  // Usage
  $body = json_encode(['asset' => 'BTC']);
  $headers = buildAuthHeaders('/dac/v1/create-wallet', $body);
  print_r($headers);
  ```
</CodeGroup>

## Complete Request Example

The following shows a fully authenticated `curl` request that creates a BTC wallet, illustrating how all authentication pieces come together:

```shell theme={null}
# 1. Generate the JWT for this specific request (use your language snippet)
JWT="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."

# 2. Send the request with both required auth headers
curl --request POST \
  --url https://api.finrock.io/dac/v1/create-wallet \
  --header "Authorization: Bearer ${JWT}" \
  --header "x-api-key: 4466c45a-7b28-4c50-a0f7-198f8d7f34c5" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{"asset":"BTC"}'
```

## Authentication Checklist

Use this checklist to verify your implementation before going to production:

<Accordion title="Authentication implementation checklist">
  * [ ] RSA key pair generated with a minimum of 4096 bits
  * [ ] Public key uploaded to the Finrock control panel and API key UUID copied
  * [ ] Private key stored in a secrets manager, not in source code or environment variables in plain text
  * [ ] JWT payload includes all six required fields: `uri`, `nonce`, `iat`, `exp`, `sub`, `bodyHash`
  * [ ] `exp` is set to less than `iat + 30` seconds
  * [ ] `nonce` is unique per request (UUID v4 recommended)
  * [ ] `bodyHash` is computed from the exact raw body string sent in the request
  * [ ] JWT is signed with RS256 (not HS256 or any other algorithm)
  * [ ] Both `Authorization: Bearer <JWT>` and `x-api-key` headers are present on every authenticated request
  * [ ] JWT is regenerated fresh for each request — do not reuse tokens
</Accordion>

## Troubleshooting

<Accordion title="401 Unauthorized — token rejected">
  Check the following in order:

  1. Confirm `x-api-key` matches the UUID shown in the control panel for your API User.
  2. Verify the public key uploaded to the control panel corresponds to the private key you are using to sign.
  3. Ensure `exp` is less than `iat + 30`. Clocks that are out of sync can cause valid-looking tokens to be rejected.
  4. Confirm `bodyHash` was computed from the exact bytes sent as the request body. Even a single whitespace difference will produce a different hash.
  5. Verify the JWT algorithm header is `RS256`, not `HS256`.
</Accordion>

<Accordion title="Nonce reuse errors">
  Each request must use a unique `nonce` value. Using a UUID v4 generated fresh for each request is the simplest approach. Do not copy nonces from previous requests or log them for reuse.
</Accordion>
