# Authentication

> How to sign every request with HMAC-SHA256 in the SB1-HMAC-SHA256 scheme, with validation values and Node.js and Python code.

Canonical URL: https://apis.cortecloud.com.br/docs/en/comecando/autenticacao/

Every request to the API is signed with HMAC-SHA256 under the `SB1-HMAC-SHA256` scheme. There is no login, session, cookie or token to renew: the same secret key signs every call until it is replaced.

## Credentials {#credenciais}

Serrabits provides your integration with two credentials per environment. To get them, write to suporte@serrabits.com.br.

| Credential | Use |
| --- | --- |
| **api key** | Identifies your integration. It goes in the `Authorization` header of every request. |
| **secret key** | Generates the signature of each request. It is never sent: only the signature goes in the request. |

The secret key must stay in your backend only. Do not put it in frontend code, a mobile app or anywhere reachable from the user's browser: whoever has the secret key can make calls on behalf of your integration. If it leaks, write to suporte@serrabits.com.br to have it revoked and receive a new one.

## Headers {#cabecalhos}

Every request carries two headers:

```text
Authorization: SB1-HMAC-SHA256 api-key="<api key>", signed-headers="host", signature="<signature>"
x-company-internal-code: <service center code>
```

- `Authorization` identifies your integration and carries the request signature, computed as described below.
- `x-company-internal-code` indicates the service center the request acts on (see [service center code](https://apis.cortecloud.com.br/docs/en/comecando/conceitos.md#codigo-da-central)). This header is not part of the signature.

Requests with a body also send `Content-Type: application/json`.

The signature covers method, path, query string, host and body. Changing any of them after signing invalidates the request, and a captured request cannot be reused with a different path, method or body.

## How to calculate the signature {#como-calcular-a-assinatura}

1. Build the signed text, one item per line, separated by `\n` (an actual line break, not the characters `\` and `n`):
   1. HTTP method in uppercase (`GET`, `POST`, `PUT`, `PATCH`);
   2. URL path, without host or query string (`/materials/boards`); an empty path counts as `/`;
   3. query string parameters sorted by name (and, for repeated names, by value), each as `name=value` encoded as JavaScript's `encodeURIComponent` does, joined by `&`; without a query string, an empty line;
   4. `host:<host>` followed by a blank line, where `<host>` is exactly the value your HTTP library sends in the `host` header (the hostname, plus `:port` only when the port is not the protocol's default);
   5. the word `host`, which is the list of signed headers;
   6. hexadecimal SHA-256 of the body bytes; without a body, the SHA-256 of the empty string (`e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`).
2. Calculate the HMAC-SHA256 of this text using the secret key as the key.
3. Use the result in lowercase hexadecimal as `<signature>`.

The blank line after `host:<host>` is part of the text: the host line ends with its own line break, added to the `\n` that separates the parts.

### Example: GET {#exemplo-get}

Signed text for `GET /materials/boards?limit=10` on host `api.example.com`, without a body:

```text
GET
/materials/boards
limit=10
host:api.example.com

host
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
```

### Example: POST {#exemplo-post}

Signed text for `POST /services/123/production` on host `api.example.com`, with the body `{"sellerEmail":"vendedor@example.com"}`. The hash is calculated over the exact bytes of the body sent: reformatting the JSON after signing (spaces, line breaks, key order) invalidates the signature.

```text
POST
/services/123/production

host:api.example.com

host
d4f22123a7a0685bd98bb9f7fc9b3db455e5b4e0c4420f0e0ddedd4826a3e0cc
```

Without a query string, the third line is empty.

### Validate your implementation {#valide-a-sua-implementacao}

With the test secret key `sb_sk_exemplo`, the two texts above produce these signatures:

| Example | Signature |
| --- | --- |
| GET | `e2a240cea084ff4950cc78e1d48d6da563aae9f7935a8dfaa28c3672598ef344` |
| POST | `8ac32656f8233168640afaff2f7c6213e42dd0fb9cdbd3f81b2ae666cab94e2b` |

If your implementation arrives at the same values, it is building the text the way the API expects.

## Reference code {#codigo-de-referencia}

### Node.js {#nodejs}

```js
const crypto = require('crypto');

function canonicalQueryString(url) {
  const entries = [...url.searchParams.entries()].sort(([ak, av], [bk, bv]) =>
    ak === bk ? av.localeCompare(bv) : ak.localeCompare(bk),
  );
  return entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&');
}

function buildCanonicalRequest(method, url, body = '') {
  const payloadHash = crypto.createHash('sha256').update(body, 'utf-8').digest('hex');
  return [
    method.toUpperCase(),
    url.pathname || '/',
    canonicalQueryString(url),
    `host:${url.host}\n`,
    'host',
    payloadHash,
  ].join('\n');
}

const url = new URL('https://api.example.com/materials/boards?limit=10');
const canonicalRequest = buildCanonicalRequest('GET', url);
const signature = crypto.createHmac('sha256', secretKey).update(canonicalRequest).digest('hex');

const authorization = `SB1-HMAC-SHA256 api-key="${apiKey}", signed-headers="host", signature="${signature}"`;
```

### Python {#python}

```python
# urllib.parse.quote with safe="!*'()" reproduces the same set
# of characters that JavaScript's encodeURIComponent leaves unescaped.
import hashlib
import hmac
import urllib.parse


def encode(value):
    return urllib.parse.quote(str(value), safe="!*'()")


def canonical_query_string(query_pairs):
    return "&".join(
        f"{encode(k)}={encode(v)}"
        for k, v in sorted(query_pairs, key=lambda kv: (kv[0], kv[1]))
    )


def build_canonical_request(method, path, query_pairs, host, body=b""):
    payload_hash = hashlib.sha256(body).hexdigest()
    return "\n".join([
        method.upper(),
        path or "/",
        canonical_query_string(query_pairs),
        f"host:{host}\n",
        "host",
        payload_hash,
    ])


canonical_request = build_canonical_request("GET", "/materials/boards", [("limit", "10")], "api.example.com")
signature = hmac.new(secret_key.encode(), canonical_request.encode(), hashlib.sha256).hexdigest()

authorization = f'SB1-HMAC-SHA256 api-key="{api_key}", signed-headers="host", signature="{signature}"'
```

Do not set the `host` header manually in the HTTP call: the library already sends it based on the URL. Just make sure the value used for signing (`url.host` and `host` in the examples) is exactly what it sends; in most libraries, it is the same.

For requests with a body, sign and send the same bytes. Serialize the JSON once, keep the string and use it both in the hash and in the request body.

## When authentication fails {#quando-a-autenticacao-falha}

The API responds with `401` when:

- `Authorization` or `x-company-internal-code` is missing or malformed;
- the api key is unknown or has been revoked;
- the service center indicated in `x-company-internal-code` is not linked to the api key or is not active on Cortecloud;
- `signed-headers` names a header the request did not send;
- the signature does not match the received request.

To diagnose a signature that does not match, compare the text you signed with the format above, line by line: the most common causes are the missing blank line after the host, the query string out of order and the body reformatted between hashing and sending.
