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

# Authentication & Signing

# Authentication & Signing

Accessing OristaPay Open APIs requires **dual authentication**:

* **OAuth2 Access Token** — proves the caller's identity
* **HMAC Request Signature** — proves the request has not been tampered with

> Every request must pass both layers of verification. Failure of either results in `401 Unauthorized`.

## 1. Obtain Access Token

Standard OAuth2 `client_credentials` flow.

**Request**

```
POST /realms/digitalasset/protocol/openid-connect/token HTTP/1.1
Host: auth.uat.rdezlink.tech
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id={api_key}
&client_secret={api_secret}
```

**Response**

```json theme={null}
{
  "access_token": "eyJhbGciOi...",
  "token_type":   "Bearer",
  "expires_in":   300
}
```

**Usage Constraints**

* `access_token` can be reused within `expires_in` seconds. **Cache it** and refresh proactively 30 seconds before expiry.
* `access_token` must be issued by the OAuth client corresponding to `X-Api-Key`.

<Warning>
  Using merchant A's `api_key` with merchant B's `access_token` will be rejected.
</Warning>

## 2. Request Signature Algorithm

**String to Sign**

```
string_to_sign = METHOD + PATH + TIMESTAMP + NONCE + SHA256_HEX(BODY)
```

**Signature Calculation**

```
signature = HEX( HMAC_SHA256( sign_secret, string_to_sign ) )
```

**Field Definitions**

| Element     | Definition                                                                                          |
| ----------- | --------------------------------------------------------------------------------------------------- |
| `METHOD`    | HTTP method, uppercase, e.g. `POST`                                                                 |
| `PATH`      | API path without domain and query, e.g. `/api/v1/wallet/list`                                       |
| `TIMESTAMP` | 13-digit UTC millisecond timestamp string, must match `X-Timestamp` exactly                         |
| `NONCE`     | Unique random string for this request, must match `X-Nonce` exactly. Recommended: 32 hex characters |
| `BODY`      | Raw request body bytes; empty string when no body (`SHA256_HEX("")` = `e3b0c442...b855`)            |

**Reference Implementations**

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import hashlib
    import hmac

    def sign(method: str, path: str, ts: str, nonce: str,
             body: str, sign_secret: str) -> str:
        body_hash = hashlib.sha256(body.encode()).hexdigest()
        to_sign   = method + path + ts + nonce + body_hash
        return hmac.new(sign_secret.encode(),
                        to_sign.encode(),
                        hashlib.sha256).hexdigest()
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import javax.crypto.Mac;
    import javax.crypto.spec.SecretKeySpec;
    import java.security.MessageDigest;
    import java.util.HexFormat;

    public static String sign(String method, String path, String ts,
                              String nonce, byte[] body, String signSecret)
                              throws Exception {
        String bodyHash = HexFormat.of().formatHex(
            MessageDigest.getInstance("SHA-256").digest(body));
        String toSign = method + path + ts + nonce + bodyHash;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(signSecret.getBytes(), "HmacSHA256"));
        return HexFormat.of().formatHex(mac.doFinal(toSign.getBytes()));
    }
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    const crypto = require('crypto');

    function sign(method, path, ts, nonce, body, signSecret) {
      const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
      const toSign = method + path + ts + nonce + bodyHash;
      return crypto.createHmac('sha256', signSecret)
                   .update(toSign).digest('hex');
    }
    ```
  </Tab>
</Tabs>

## 3. Request Header Specification

All business requests must carry the following headers:

| Header          | Required | Description                                                                            |
| --------------- | -------- | -------------------------------------------------------------------------------------- |
| `Authorization` | ✓        | `Bearer {access_token}`                                                                |
| `X-Api-Key`     | ✓        | Your `api_key`                                                                         |
| `X-Timestamp`   | ✓        | UTC millisecond timestamp, **±5 minutes** tolerance                                    |
| `X-Nonce`       | ✓        | Unique random string, **must not be reused within 5 minutes** under the same `api_key` |
| `X-Signature`   | ✓        | Signature computed per §2                                                              |
| `Content-Type`  | ✓        | Always `application/json; charset=utf-8`                                               |

# Request & Response Conventions

**Protocol Specification**

| Item         | Specification                                                          |
| ------------ | ---------------------------------------------------------------------- |
| Transport    | HTTPS (TLS 1.2+)                                                       |
| Method       | `POST`                                                                 |
| Charset      | UTF-8                                                                  |
| Format       | JSON                                                                   |
| Field naming | `camelCase`                                                            |
| Timestamp    | Milliseconds; string or number accepted in request, number in response |

## Response Envelope

All responses use a **single-layer** JSON `{ code, message, data? }`, returned by the downstream business service without additional gateway wrapping.

**Success (Business OK)**

Business code `code = 1`, business data in `data`:

```json theme={null}
{
  "code":    1,
  "message": "Success",
  "data":    { /* business data; array for list endpoints */ }
}
```

**Business / Downstream Error**

Downstream business errors still return HTTP `200`, with error details expressed by `code` / `message`:

```json theme={null}
{
  "code":    <non-1 integer>,
  "message": "<failure reason>"
}
```

When downstream gRPC is unreachable or times out, HTTP status remains `200`, `code` is a negative integer (negated gRPC StatusCode, e.g. `-14` for `UNAVAILABLE`), with connection error details in `message`.

**Gateway-Level Error**

Route not found, internal gateway errors, missing descriptors, etc. HTTP status uses the corresponding error code (e.g. `400` / `404` / `500` / `502`), with response body still using `{code, message}`:

```json theme={null}
{
  "code": 404,
  "message": "route not found"
}
```

<Note>
  * HTTP status reflects **transport layer** results: `2xx` means the request was successfully delivered and converted; non-`2xx` indicates a gateway-side error
  * Business code `code` reflects **business layer** results: `1` means business success with result in `data`; other values are business error codes with reason in `message`
  * **How to determine success**: HTTP `2xx` + business `code == 1`. Both conditions must be met
  * **Authentication errors**: Any failure in `Authorization` / `X-Signature` / `X-Timestamp` / `X-Nonce` returns HTTP `401` with `{"code":401,"message":"Unauthorized"}`
</Note>

# Error Handling

## HTTP Status Codes & Business Codes

All responses use the single-layer envelope `{code, message, data?}`. HTTP status reflects gateway/transport results; business code `code` reflects business processing results.

| HTTP Status         | Meaning                                    | Response Body                                     | Action                                     |
| ------------------- | ------------------------------------------ | ------------------------------------------------- | ------------------------------------------ |
| `200` + `code == 1` | Business success                           | `{code:1, message, data}`                         | Read business fields from `data`           |
| `200` + `code != 1` | Downstream business error                  | `{code, message}`                                 | Handle based on `message`                  |
| `200` + `code < 0`  | gRPC error after delivery                  | `{code, message}` (`code` is negated gRPC status) | Check downstream, retry if needed          |
| `400`               | Invalid request body or parameters         | `{code, message}`                                 | Fix and retry                              |
| `401`               | Authentication failed (OAuth or signature) | `{code:401, message}`                             | Follow the troubleshooting checklist below |
| `404`               | Endpoint not found                         | `{code, message}`                                 | Verify the path                            |
| `429`               | Rate limit triggered                       | `{code, message, ...}`                            | Back off and retry                         |
| `5xx`               | Gateway or downstream service error        | `{code, message}`                                 | Exponential backoff and retry              |

## Authentication Failure Checklist

All authentication errors return:

```json theme={null}
{ "code": 401, "message": "Unauthorized" }
```

Troubleshoot in this order:

1. **Is the token valid?** — Has it expired? Was it issued by the current `api_key`?
2. **Does the signature match?** — Do `METHOD / PATH / BODY` match the actual request?
3. **Is the timestamp within the window?** — Is the local clock synchronized with NTP?
4. **Is the nonce unique?** — Must not be reused within 5 minutes under the same `api_key`.

# Rate Limiting

| Item              | Value                     |
| ----------------- | ------------------------- |
| Dimension         | Per `api_key`             |
| Default quota     | **600 requests / minute** |
| Exceeded response | HTTP `429`                |

**Rate Limit Exceeded Response Example**

```json theme={null}
{
  "code":      429,
  "message":   "rate limit exceeded",
  "limit":     600,
  "window_ms": 60000
}
```

> For higher quotas, contact your account manager. Adjustments take effect the following minute.

# Security Recommendations

| Topic                 | Best Practice                                                                                               |
| --------------------- | ----------------------------------------------------------------------------------------------------------- |
| Key management        | Store `api_secret` and `sign_secret` server-side only. **Never expose them to frontend or mobile clients.** |
| Credential rotation   | Rotate regularly; contact your account manager immediately if credentials are leaked                        |
| Transport security    | Enforce HTTPS, reject any client not using TLS 1.2+                                                         |
| Clock synchronization | Use NTP to maintain accuracy within ±1 minute to avoid false rejections                                     |
| Log sanitization      | Never log full `api_secret` / `sign_secret` / `access_token` in application logs                            |
