# API keys and signing

Create a key in
[API-key settings](https://barebitcoin.no/innlogget/profil/nokler). The key
starts with `bb/public/`; the secret starts with `bb/apisecret/`. Both are
credentials. Do not put them in public source code or URLs.

## Headers

Read requests normally require only `x-bb-api-key`. Signed requests require all
three headers:

| Header           | Value                                                                     |
| ---------------- | ------------------------------------------------------------------------- |
| `x-bb-api-key`   | Complete API key                                                          |
| `x-bb-api-nonce` | Unsigned decimal integer greater than the last accepted nonce for the key |
| `x-bb-api-hmac`  | Base64-encoded HMAC-SHA256 signature                                      |

Enable trading permission for creating and cancelling orders. Enable bitcoin
withdrawal permission for initiating withdrawals.

## Signature calculation

```text
digest = SHA256(UTF8(decimalNonce) || rawRequestBodyBytes)
message = UTF8(uppercaseMethod) || UTF8(uriPath) || digest
signature = Base64(HMAC_SHA256(Base64Decode(completeSecret), message))
```

Decode the complete secret, including its prefix. The signed path is the decoded
URL path and excludes the query string. For a bodyless request, use zero body
bytes. Serialize a JSON body once and send precisely those signed bytes.

## Nonce validation and retries

Coordinate signed requests per key. A timestamp alone does not prevent
collisions between concurrent requests or out-of-order arrival. The server can
consume a nonce before an operation fails; the next attempt needs a larger
nonce.

After a timeout, inspect orders or withdrawals before retrying. A nonce is not
an idempotency key, and repeating a request can create another transaction.

## Node.js signing helper

Save the following as `bb-request.mjs`. Set `BB_API_KEY`, `BB_API_SECRET`, and a
coordinated `BB_NONCE` in your environment. It sends requests to production only
when explicitly executed. Replace example destination and path identifiers
before use.



```js
import { createHash, createHmac } from 'node:crypto';
import { readFile } from 'node:fs/promises';

export function signature(method, path, nonce, body, secret) {
  const digest = createHash('sha256').update(nonce).update(body).digest();
  return createHmac('sha256', Buffer.from(secret, 'base64'))
    .update(method.toUpperCase())
    .update(path)
    .update(digest)
    .digest('base64');
}

// Importing the helper never sends a request.
if (process.argv[1] && import.meta.url === new URL(process.argv[1], 'file:').href) {
  const [method, path, bodyFile] = process.argv.slice(2);
  const { BB_API_KEY: key, BB_API_SECRET: secret, BB_NONCE: nonce } = process.env;
  if (!key || !secret || !nonce || !/^\d+$/.test(nonce) || !method || !path?.startsWith('/v1/')) {
    throw new Error('Set BB_API_KEY, BB_API_SECRET, BB_NONCE; pass METHOD /v1/path [body.json].');
  }
  const body = bodyFile ? await readFile(bodyFile) : Buffer.alloc(0);
  const url = new URL(path, 'https://api.bb.no');
  const headers = {
    'x-bb-api-key': key,
    'x-bb-api-nonce': nonce,
    'x-bb-api-hmac': signature(method, decodeURIComponent(url.pathname), nonce, body, secret),
    ...(bodyFile ? { 'Content-Type': 'application/json' } : {}),
  };
  const response = await fetch(url, {
    method,
    headers,
    body: bodyFile ? body : undefined,
    redirect: 'error',
  });
  const result = await response.text();
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${result}`);
  console.log(result);
}

```



For a bodyless cancellation:

```sh
node bb-request.mjs DELETE '/v1/orders/REPLACE_ORDER_ID'
```

For an order, save the request body to `body.json`, then run:

```sh
node bb-request.mjs POST '/v1/orders' body.json
```

## Signing fixture

For method `POST`, path `/v1/orders`, nonce `1733314678`, synthetic secret
`bb/apisecret/ZZavHDgVRyGowg8blKfPDDRlN3+6h0/vOUA`, and these exact body bytes:

```json
{ "type": "ORDER_TYPE_MARKET", "direction": "DIRECTION_BUY", "amount": 100 }
```

The expected signature is `e6pQ5w9AqVhwHRWXuwS7ZwzRd0kH2GYpHSmtP0cTlSU=`. This
is a test fixture, not a usable account credential.

## Go signing helper

This function constructs a signed request without sending it. Pass the body
bytes you intend to send, the complete secret, and a coordinated nonce. Execute
the returned request with an HTTP client that has a timeout and rejects
redirects.

```go
package signing

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"
	"net/http"
	"strings"
)

func SignedRequest(method, path, key, secret, nonce string, body []byte) (*http.Request, error) {
	if !strings.HasPrefix(path, "/v1/") {
		return nil, fmt.Errorf("path must start with /v1/")
	}
	secretBytes, err := base64.StdEncoding.DecodeString(secret)
	if err != nil {
		return nil, fmt.Errorf("decode API secret: %w", err)
	}
	req, err := http.NewRequest(strings.ToUpper(method), "https://api.bb.no"+path, bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}
	digest := sha256.Sum256(append([]byte(nonce), body...))
	message := append([]byte(req.Method+req.URL.Path), digest[:]...)
	mac := hmac.New(sha256.New, secretBytes)
	if _, err := mac.Write(message); err != nil {
		return nil, fmt.Errorf("sign request: %w", err)
	}
	req.Header.Set("x-bb-api-key", key)
	req.Header.Set("x-bb-api-nonce", nonce)
	req.Header.Set("x-bb-api-hmac", base64.StdEncoding.EncodeToString(mac.Sum(nil)))
	if len(body) > 0 {
		req.Header.Set("Content-Type", "application/json")
	}
	return req, nil
}
```


Source: https://barebitcoin.no/developers/authentication/api-keys
