> For the complete documentation index, see [llms.txt](https://docs-swap.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://docs-swap.crypto-chief.com/getting-started/authentication.md).

# Authentication

## Authentication

The key in the path is your **project UUID**: the **Merchant ID** shown in the Dashboard under **Project Settings → Integration**. It is the same key as in your RPC Gateway URLs, and Swap API calls spend the same platform API credits.

It is not the secret **API key** that signs requests on the other Crypto Chief APIs. Never put that one in a URL.

The key goes **in the request path**. There is no signature and no authorization header:

```
https://swap-api.crypto-chief.com/YOUR_API_KEY/v1/quote
```

Every operation is a `POST` with a JSON object body to `https://swap-api.crypto-chief.com/YOUR_API_KEY/v1/<operation>`. Send `Content-Type: application/json`.

{% hint style="warning" %}
**Keep the key server-side.** For the Swap API the project UUID is the only credential: anyone who has the full URL can spend your credits. Do not put it in browser code, mobile apps, or public repositories. A project's domain or IP whitelist applies to Swap API calls the same way as to other platform calls (`403 DOMAIN_NOT_ALLOWED` / `403 IP_NOT_ALLOWED`).
{% endhint %}

An unknown or frozen key is refused with `401 UNAUTHORIZED` before anything runs:

```json
{
  "error": "UNAUTHORIZED",
  "message": "invalid project key"
}
```

### Request ID

Every response to one of the operations carries an `x-request-id` header (`404` and `405` do not). Send your own as `X-Request-Id` — 1 to 128 characters of `[A-Za-z0-9._:-]` — and the same value comes back; anything else is replaced with a generated one. Quote it when contacting support about a specific call.

## Calling the API

Each tab below defines `swap(operation, body)`: it POSTs the body as JSON to `https://swap-api.crypto-chief.com/YOUR_API_KEY/v1/<operation>` and returns the parsed JSON together with the HTTP status. The key is read from the environment variable `CC_API_KEY`. Every example in this documentation calls this helper.

On success the result holds the response fields (`amount_out`, `steps`, ...). On failure it holds `error` and `message` — see [Errors](/getting-started/errors.md).

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

```javascript
const API_KEY = process.env.CC_API_KEY;
const BASE = "https://swap-api.crypto-chief.com";

async function swap(operation, body) {
  const res = await fetch(`${BASE}/${API_KEY}/v1/${operation}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body ?? {}),
  });
  const data = await res.json().catch(() => ({}));
  return { status: res.status, ...data }; // on error: {status, error, message}
}

// Example: quote 1000 USDC to ETH on Ethereum.
const quote = await swap("quote", {
  network: "ETH_MAINNET",
  token_in: "USDC",
  token_out: "NATIVE",
  amount_in: "1000000000",
});
if (quote.error) throw new Error(`${quote.status} ${quote.error}: ${quote.message}`);
console.log(quote.amount_out_human, "ETH expected");
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
const BASE = 'https://swap-api.crypto-chief.com';

function swap(string $operation, array $body = []): array {
    $ch = curl_init(BASE . '/' . getenv('CC_API_KEY') . '/v1/' . $operation);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        // An empty array would encode as [], the API expects an object.
        CURLOPT_POSTFIELDS     => json_encode($body ?: new stdClass(), JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR),
    ]);
    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);
    $data = json_decode((string) $raw, true) ?: [];
    return ['status' => $status] + $data; // on error: status, error, message
}

// Example: quote 1000 USDC to ETH on Ethereum.
$quote = swap('quote', [
    'network'   => 'ETH_MAINNET',
    'token_in'  => 'USDC',
    'token_out' => 'NATIVE',
    'amount_in' => '1000000000',
]);
if (isset($quote['error'])) throw new RuntimeException("{$quote['status']} {$quote['error']}: {$quote['message']}");
echo $quote['amount_out_human'], " ETH expected\n";
```

{% endtab %}

{% tab title="GO" %}

```go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

var apiKey = os.Getenv("CC_API_KEY")

const base = "https://swap-api.crypto-chief.com"

// swap POSTs body as JSON to /v1/<operation> and returns the decoded body and
// the HTTP status. Error responses decode to {"error", "message"}.
func swap(operation string, body any) (map[string]any, int, error) {
	raw, err := json.Marshal(body)
	if err != nil {
		return nil, 0, err
	}
	req, err := http.NewRequest(http.MethodPost, base+"/"+apiKey+"/v1/"+operation, bytes.NewReader(raw))
	if err != nil {
		return nil, 0, err
	}
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, 0, err
	}
	defer res.Body.Close()
	payload, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, res.StatusCode, err
	}
	out := map[string]any{}
	if err := json.Unmarshal(payload, &out); err != nil {
		return nil, res.StatusCode, fmt.Errorf("HTTP %d: %s", res.StatusCode, payload)
	}
	return out, res.StatusCode, nil
}

func main() {
	// Example: quote 1000 USDC to ETH on Ethereum.
	quote, status, err := swap("quote", map[string]any{
		"network":   "ETH_MAINNET",
		"token_in":  "USDC",
		"token_out": "NATIVE",
		"amount_in": "1000000000",
	})
	if err != nil {
		log.Fatal(err)
	}
	if e, ok := quote["error"]; ok {
		log.Fatalf("%d %s: %s", status, e, quote["message"])
	}
	fmt.Println(quote["amount_out_human"], "ETH expected")
}
```

{% endtab %}

{% tab title="Python" %}

```python
import os
import requests

API_KEY = os.environ["CC_API_KEY"]
BASE = "https://swap-api.crypto-chief.com"

def swap(operation, body=None):
    r = requests.post(f"{BASE}/{API_KEY}/v1/{operation}", json=body or {})
    try:
        data = r.json()
    except ValueError:
        data = {}
    data["status"] = r.status_code  # on error: {"error", "message", "status"}
    return data

# Example: quote 1000 USDC to ETH on Ethereum.
quote = swap("quote", {
    "network": "ETH_MAINNET",
    "token_in": "USDC",
    "token_out": "NATIVE",
    "amount_in": "1000000000",
})
if "error" in quote:
    raise RuntimeError(f"{quote['status']} {quote['error']}: {quote['message']}")
print(quote["amount_out_human"], "ETH expected")
```

{% endtab %}

{% tab title="curl" %}

```bash
export CC_API_KEY="your-project-uuid"

# Example: quote 1000 USDC to ETH on Ethereum.
curl -s -X POST "https://swap-api.crypto-chief.com/$CC_API_KEY/v1/quote" \
  -H "Content-Type: application/json" \
  -d '{"network":"ETH_MAINNET","token_in":"USDC","token_out":"NATIVE","amount_in":"1000000000"}'
```

{% endtab %}
{% endtabs %}

## What goes wrong

| Symptom                  | Cause                                                                                 |
| ------------------------ | ------------------------------------------------------------------------------------- |
| `401 UNAUTHORIZED`       | The key in the path is unknown, or the project is frozen                              |
| `403 DOMAIN_NOT_ALLOWED` | The project whitelist of domains is on and the `Origin`/`Referer` domain is not in it |
| `403 IP_NOT_ALLOWED`     | The project whitelist of IPs is on and the caller's IP is not in it                   |
| `404 NOT_FOUND`          | The path after the key is not one of the operations — check for a typo in `/v1/...`   |
| `405 METHOD_NOT_ALLOWED` | The path exists but only as `POST`                                                    |

All five are refused before the call runs and are never charged — see [Billing](/getting-started/billing.md).
