> For the complete documentation index, see [llms.txt](https://tron-energy-doc.crypto-chief.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tron-energy-doc.crypto-chief.com/getting-started/authentication.md).

# Authentication

## Authentication

Every request carries two headers: the project it comes from, and a signature proving it was made by someone holding that project's API key.

```
Merchant:  1f8c3a90-4d2e-4b71-9e0a-7c5f2b6d8134
Signature: 36c168a99d535a706a79cc8d19d1ed30
```

**Your API key is never sent.** It is the secret the signature is computed with, and it stays on your server.

{% hint style="warning" %}
The same key signs requests on every other Crypto Chief API, including ones that move funds. Anything that would put it on the wire — a bearer header, a query parameter, a browser — hands over that ability too. Sign; do not send.
{% endhint %}

### Where to find your credentials

Both are in the Dashboard under **Project Settings**:

| Header      | What it is                                                                                                      |
| ----------- | --------------------------------------------------------------------------------------------------------------- |
| `Merchant`  | Your **Merchant ID** — the project UUID, shown in the dashboard under Integration. An identifier, not a secret. |
| `Signature` | Computed per request from the body and your **API key**.                                                        |

Rotating the API key in the Dashboard takes effect within about thirty seconds, after which signatures made with the old key stop verifying.

## How to sign a request

Three steps.

### 1. Canonicalise the body

Parse the JSON body and re-serialise it with **object keys sorted** and **no insignificant whitespace**. This is what makes the signature independent of how your JSON library happens to order or format fields.

```
{"duration_sec":3600,"receive_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}
```

### 2. Base64 the result, then append your API key

```
base64(canonical) + api_key
```

Standard base64 with padding. The key is concatenated directly — no separator, no newline.

### 3. MD5 it, lowercase hex

```
Signature = hex( md5( base64(canonical) + api_key ) )
```

Thirty-two lowercase hex characters.

## Check your implementation

These vectors use the API key `test-api-key-123`. If your code reproduces all three, it will sign correctly.

| Body                                                                           | Signature                          |
| ------------------------------------------------------------------------------ | ---------------------------------- |
| *(empty)*                                                                      | `243803a7915cfddb629813ae00da7da3` |
| `{"receive_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}`                     | `36c168a99d535a706a79cc8d19d1ed30` |
| `{"duration_sec":3600,"receive_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}` | `4404db94fb48bf9b794cd555c02589d3` |

The third is the one that catches mistakes: send those two fields in either order, pretty-printed or compact, and the signature must still be `4404db…`. If it changes, your canonicalisation is not sorting keys or is leaving whitespace in.

## Examples

{% tabs %}
{% tab title="JavaScript" %}

```javascript
import crypto from "node:crypto";

const API_KEY = "your-api-key";
const MERCHANT = "your-project-uuid";
const BASE = "https://energy.crypto-chief.com";

// JSON.stringify does not sort keys, so sort them yourself.
function canonical(body) {
  if (body === null || body === undefined) return "";
  return JSON.stringify(body, Object.keys(body).sort());
}

function sign(body, apiKey) {
  const payload = Buffer.from(canonical(body)).toString("base64") + apiKey;
  return crypto.createHash("md5").update(payload).digest("hex");
}

async function post(path, body) {
  const raw = canonical(body);
  const res = await fetch(BASE + path, {
    method: "POST",
    body: raw,                          // send exactly what you signed
    headers: {
      "Content-Type": "application/json",
      Merchant: MERCHANT,
      Signature: sign(body, API_KEY),
    },
  });
  return res.json();
}

console.log(await post("/v1/quotes", {
  receive_address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
}));
```

{% hint style="warning" %}
`JSON.stringify(body, Object.keys(body).sort())` sorts only the **top level**. Every request body on this API is flat, so that is enough here — but if you reuse the helper elsewhere, sort recursively.
{% endhint %}
{% endtab %}

{% tab title="PHP" %}

```php
<?php
const API_KEY  = 'your-api-key';
const MERCHANT = 'your-project-uuid';
const BASE     = 'https://energy.crypto-chief.com';

function canonical(?array $body): string {
    if ($body === null) return '';
    ksort($body);
    return json_encode($body, JSON_UNESCAPED_SLASHES);
}

function sign(?array $body, string $apiKey): string {
    return md5(base64_encode(canonical($body)) . $apiKey);
}

function post(string $path, array $body) {
    $raw = canonical($body);
    $ch  = curl_init(BASE . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $raw,       // send exactly what you signed
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'Merchant: ' . MERCHANT,
            'Signature: ' . sign($body, API_KEY),
        ],
    ]);
    return json_decode(curl_exec($ch), true);
}

print_r(post('/v1/quotes', [
    'receive_address' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
]));
```

{% endtab %}

{% tab title="GO" %}

```go
package main

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"net/http"
)

const (
	apiKey   = "your-api-key"
	merchant = "your-project-uuid"
	base     = "https://energy.crypto-chief.com"
)

// json.Marshal sorts map keys, so a map canonicalises for free.
func canonical(body map[string]any) ([]byte, error) {
	if body == nil {
		return nil, nil
	}
	return json.Marshal(body)
}

func sign(canonical []byte, apiKey string) string {
	payload := base64.StdEncoding.EncodeToString(canonical) + apiKey
	sum := md5.Sum([]byte(payload))
	return hex.EncodeToString(sum[:])
}

func post(path string, body map[string]any) (*http.Response, error) {
	raw, err := canonical(body)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", base+path, bytes.NewReader(raw))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Merchant", merchant)
	req.Header.Set("Signature", sign(raw, apiKey))
	return http.DefaultClient.Do(req)
}
```

{% hint style="info" %}
Use a `map[string]any` rather than a struct. `json.Marshal` sorts map keys but emits struct fields in declaration order, so a struct signs differently depending on how it is written.
{% endhint %}
{% endtab %}

{% tab title="Python" %}

```python
import base64, hashlib, json, requests

API_KEY  = "your-api-key"
MERCHANT = "your-project-uuid"
BASE     = "https://energy.crypto-chief.com"

def sign(body: dict | None, api_key: str) -> str:
    canonical = "" if body is None else json.dumps(
        body, separators=(",", ":"), sort_keys=True)
    payload = base64.b64encode(canonical.encode()).decode() + api_key
    return hashlib.md5(payload.encode()).hexdigest()

def post(path: str, body: dict):
    canonical = json.dumps(body, separators=(",", ":"), sort_keys=True)
    return requests.post(
        BASE + path,
        data=canonical,                      # send exactly what you signed
        headers={
            "Content-Type": "application/json",
            "Merchant":     MERCHANT,
            "Signature":    sign(body, API_KEY),
        },
    )

r = post("/v1/quotes", {"receive_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"})
print(r.json())
```

{% endtab %}

{% tab title="curl" %}

```bash
API_KEY="your-api-key"
MERCHANT="your-project-uuid"

# Build the canonical body once and use it for both the signature and the request.
BODY='{"receive_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"}'

SIG=$(printf '%s' "$BODY" | base64 -w0 \
      | { read -r b64; printf '%s%s' "$b64" "$API_KEY"; } \
      | md5sum | cut -d' ' -f1)

curl https://energy.crypto-chief.com/v1/quotes \
  -H "Content-Type: application/json" \
  -H "Merchant: $MERCHANT" \
  -H "Signature: $SIG" \
  -d "$BODY"
```

On macOS use `base64` without `-w0` and `md5 -q` instead of `md5sum`.
{% endtab %}
{% endtabs %}

### Signing a GET request

A `GET` has no body, so there is nothing to canonicalise. **The signature is the MD5 of your API key** — the same value for every `GET` you ever make.

Compute it once and keep it:

```
Signature = md5(api_key)
```

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const GET_SIG = crypto.createHash("md5").update(API_KEY).digest("hex");
```

{% endtab %}

{% tab title="PHP" %}

```php
$getSig = md5(API_KEY);
```

{% endtab %}

{% tab title="GO" %}

```go
sum := md5.Sum([]byte(apiKey))
getSig := hex.EncodeToString(sum[:])
```

{% endtab %}

{% tab title="Python" %}

```python
get_sig = hashlib.md5(API_KEY.encode()).hexdigest()
```

{% endtab %}

{% tab title="curl" %}

```bash
GET_SIG=$(printf "%s" "$API_KEY" | md5sum | cut -d" " -f1)

curl https://energy.crypto-chief.com/v1/balance \
  -H "Merchant: $MERCHANT" \
  -H "Signature: $GET_SIG"
```

{% endtab %}
{% endtabs %}

## Send exactly what you signed

The signature covers the bytes of the body as they arrive. If your HTTP client re-serialises the object after you signed it — adding whitespace, reordering fields, changing number formatting — the signature will not match what arrives.

Build the canonical string once, sign **that**, and send **that**. Every example above does exactly this.

## What goes wrong

| Symptom                                          | Cause                                                                            |
| ------------------------------------------------ | -------------------------------------------------------------------------------- |
| `INVALID_SIGNATURE` on a body you are sure about | Your client re-serialised after signing. Send the same bytes you signed.         |
| Works with one field, fails with two             | Keys are not being sorted.                                                       |
| Works from curl, fails from your app             | Your JSON library is pretty-printing. Canonical form has no whitespace.          |
| Works on `POST`, fails on `GET`                  | A `GET` signs the **empty string**, not the URL or the path.                     |
| Every request fails after a key rotation         | The signature is computed with the old key; take the new one from the Dashboard. |

## Errors

| Status | Code                | What happened                                                 |
| ------ | ------------------- | ------------------------------------------------------------- |
| `400`  | `BAD_AUTH_HEADERS`  | `Merchant` or `Signature` is missing                          |
| `401`  | `INVALID_SIGNATURE` | The signature does not match, **or** no project has that UUID |
| `403`  | `PROJECT_FROZEN`    | The request is valid; the project is frozen                   |
| `503`  | `AUTH_UNAVAILABLE`  | We could not verify the request just now — retry              |

```json
{
  "ok": false,
  "error": "INVALID_SIGNATURE",
  "msg": "signature mismatch"
}
```

{% hint style="info" %}
`INVALID_SIGNATURE` does not tell you which of the two was wrong. Check both your `Merchant` value and your signing code.
{% endhint %}

`PROJECT_FROZEN` means your signature was correct. There is nothing to fix in your code; the project is stopped and needs unfreezing.

`AUTH_UNAVAILABLE` means we could not check, not that you are wrong. Retry with backoff rather than alerting your customer.

## What the project carries with it

Everything attached to the project applies here:

* **Your credit balance.** Orders are charged to it; there is no separate energy balance.
* **Your billing terms.** Prepaid or postpaid, and your debt limit if you have one.
* **A freeze.** A project frozen on the platform cannot buy energy either.

## Reading another project's orders

You cannot. [`GET /v1/orders/{key}`](/api-reference/get-order.md) checks that the order belongs to the project that signed the request, and answers `404 NOT_FOUND` otherwise — the same answer as a key that does not exist, so it cannot be used to discover whether one does.
