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

# Authentication

> Sign every Sotto API request with an HMAC-SHA256 signature.

Every request to the Sotto API is authenticated with an HMAC-SHA256 signature.
Sotto issues each vendor a **username** and a **secret**; the secret is used to
sign requests and is never transmitted.

## Required headers

<ParamField header="x-bigco-hmac-username" type="string" required>
  The vendor username tied to your API secret.
</ParamField>

<ParamField header="x-bigco-hmac-sha256" type="string" required>
  The Base64-encoded HMAC-SHA256 signature of the request. See
  [Generating the signature](#generating-the-signature).
</ParamField>

<ParamField header="x-date" type="string" required>
  The time the request is sent, in ISO 8601 UTC format
  (e.g. `2024-03-05T17:52:11.345Z`). This must match the value used when
  generating the signature.
</ParamField>

<ParamField header="content-type" type="string">
  Must be `application/json` for JSON requests, or `multipart/form-data` for
  file uploads.
</ParamField>

<ParamField header="x-digest" type="string">
  The Base64-encoded request body. **Required for `POST` and `PUT` requests.**
</ParamField>

## Generating the signature

The signature is computed as:

```
x-bigco-hmac-sha256 = Base64( HMAC-SHA256( API_SECRET, StringToSign ) )
```

The `StringToSign` differs by method.

<CodeGroup>
  ```text GET and DELETE theme={"dark"}
  x-date: {x_date}
  {REQUEST_METHOD} {url_path_and_query}
  ```

  ```text POST and PUT theme={"dark"}
  x-date: {x_date}
  {REQUEST_METHOD} {url_path_and_query}
  x-digest: {body_base64}
  ```
</CodeGroup>

<ParamField path="x_date" type="string">
  The request timestamp in ISO 8601 format. Must match the `x-date` header.
</ParamField>

<ParamField path="REQUEST_METHOD" type="string">
  The HTTP method in uppercase (`GET`, `POST`, `PUT`, `DELETE`).
</ParamField>

<ParamField path="url_path_and_query" type="string">
  The request path and query string after the domain — for example,
  `/api/v1/users?phone_number=+15551234567`.
</ParamField>

<ParamField path="body_base64" type="string">
  The Base64 encoding of the request body. The JSON **keys must be sorted
  alphabetically** and **all whitespace and newlines removed** before encoding.
</ParamField>

<Warning>
  The body used to compute `x-digest` must be serialized with keys sorted
  alphabetically and no extra whitespace (equivalent to
  `json.dumps(body, sort_keys=True, separators=(",", ":"))`). A signature
  computed over differently-formatted JSON will fail verification.
</Warning>

## Step by step

<Steps>
  <Step title="Retrieve your secret">
    Load the `API_SECRET` issued to you by Sotto.
  </Step>

  <Step title="Capture the timestamp">
    Generate the current time in ISO 8601 UTC format and use it for both the
    signature and the `x-date` header.
  </Step>

  <Step title="Encode the body">
    For `POST`/`PUT`, sort the JSON keys alphabetically, strip whitespace, and
    Base64-encode the result to produce `x-digest`.
  </Step>

  <Step title="Build the string to sign">
    Assemble the `StringToSign` using the timestamp, method, path with query,
    and (for `POST`/`PUT`) the Base64 body.
  </Step>

  <Step title="Sign and send">
    Compute the HMAC-SHA256 signature with your secret, then send the request
    with the `x-bigco-hmac-username`, `x-bigco-hmac-sha256`, `x-date`, and
    (for `POST`/`PUT`) `x-digest` headers.
  </Step>
</Steps>

## Example

<CodeGroup>
  ```javascript Postman pre-request script theme={"dark"}
  const CryptoJS = require('crypto-js');

  // Retrieve the secret.
  const secret = pm.environment.get('api_secret');

  // Obtain the current time.
  const x_date = new Date().toISOString();

  // Convert the body to Base64.
  const body = pm.request.body.raw || '';
  const body_base64 = Buffer.from(body, 'utf-8').toString('base64');

  const method = pm.request.method;
  const path_and_parameters = pm.request.url.getPathWithQuery();

  // Construct the string to sign.
  let signing_string = `x-date: ${x_date}\n${method} ${path_and_parameters}`;
  if (body_base64 !== '') {
    signing_string += `\nx-digest: ${body_base64}`;
    pm.request.headers.add({ key: 'x-digest', value: body_base64 });
  }

  // Generate the signature.
  const signature = CryptoJS.enc.Base64.stringify(
    CryptoJS.HmacSHA256(CryptoJS.enc.Utf8.parse(signing_string), secret)
  );

  // Set the headers.
  pm.request.headers.add({ key: 'x-date', value: x_date });
  pm.request.headers.add({ key: 'x-bigco-hmac-sha256', value: signature });
  pm.request.headers.add({ key: 'x-bigco-hmac-username', value: pm.environment.get('api_username') });
  pm.request.headers.add({ key: 'content-type', value: 'application/json' });
  ```

  ```python Python theme={"dark"}
  import base64
  import hashlib
  import hmac
  import json
  from datetime import datetime, timezone

  def sign_request(secret, method, path_and_query, body=None):
      x_date = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
      string_to_sign = f"x-date: {x_date}\n{method.upper()} {path_and_query}"

      headers = {
          "x-date": x_date,
          "x-bigco-hmac-username": "YOUR_USERNAME",
          "content-type": "application/json",
      }

      if body is not None:
          canonical = json.dumps(body, sort_keys=True, separators=(",", ":"))
          body_base64 = base64.b64encode(canonical.encode()).decode()
          string_to_sign += f"\nx-digest: {body_base64}"
          headers["x-digest"] = body_base64

      signature = base64.b64encode(
          hmac.new(secret.encode(), string_to_sign.encode(), hashlib.sha256).digest()
      ).decode()
      headers["x-bigco-hmac-sha256"] = signature
      return headers
  ```
</CodeGroup>

A failed signature returns `401 Unauthorized`:

```json theme={"dark"}
{
  "detail": {
    "type": "not_authenticated",
    "msg": "HMAC signature verification failed"
  }
}
```
