# Authentication

Every call to the REST API at `https://api.hatang.dev` is authenticated with an **API key** sent as a Bearer token. This page shows how to create keys, which scopes to give them, how to restrict them, and what the API answers when a key is missing, wrong or not allowed.

## Before you start

You need to be an owner or admin of the business, or a member with **Developer** access, to create and revoke keys ([Team and roles](https://hatang.dev/help/account/employees-and-roles)).

## Create an API key

1. In the dashboard, open **Settings → API Keys**. Email users also find the same page under **Email → API Keys**.
2. Click **Create key** and give the key a name that says where it runs, such as `production-web` or `erp-sync`.
3. Under **What this key can do**, tick the scopes it needs. Only the modules that are turned on for the business are listed.
4. Optionally restrict the key to one **Sending domain** or to **Browser origins** (see [Restricted keys](#restricted-keys)).
5. Click **Create key** and copy the key. It starts with `ht_` and is **shown only once**: Ha Tang stores only a hash of it.

The keys table shows each key's prefix, what it can do and when it was **Last used**.

## Send the key

Put the key in the `Authorization` header of every request:

```http
Authorization: Bearer ht_123...
```

**cURL**

```bash
curl "https://api.hatang.dev/emails?limit=1" \
  -H "Authorization: Bearer $HATANG_API_KEY"
```

**Node.js**

```js
import { Hatang } from "@hatang/sdk";

// The SDK sends the Authorization header for you.
const hatang = new Hatang(process.env.HATANG_API_KEY, { baseUrl: "https://api.hatang.dev" });

const { data, error } = await hatang.emails.list({ limit: 1 });
```

**Python**

```python
import os
from hatang import Hatang

# The SDK sends the Authorization header for you.
hatang = Hatang(os.environ["HATANG_API_KEY"], base_url="https://api.hatang.dev")

emails = hatang.emails.list(limit=1)
```

**PHP**

```php
$client = new GuzzleHttp\Client([
    'base_uri' => 'https://api.hatang.dev',
    'headers' => ['Authorization' => 'Bearer ' . getenv('HATANG_API_KEY')],
]);

$res = $client->get('/emails', ['query' => ['limit' => 1]]);
```

The same key also works as the SMTP password for `smtp.hatang.dev:5870` (STARTTLS), with the username `hatang`.

> **Danger: Server side only**
>
> An API key acts on behalf of your whole business. Keep it in an environment variable or a secret manager on your server. Never put it in browser JavaScript, a mobile app or a Git repository. Browsers and apps use the chat widget and the mobile SDKs, which have their own public site id and app keys.

## Scopes {#scopes}

A scope has the form `<product>:<verb>`, `<product>:*` or `*`.

| Module | Scopes | What they allow |
|---|---|---|
| Email | `email:send` | Send email: `POST /emails`, `POST /emails/batch` and the SMTP relay |
| | `email:read` | List and fetch emails, metrics and received mail |
| | `email:write` | Manage domains, templates, contacts, audiences, segments, topics, broadcasts and automations |
| Support | `chat:read`, `chat:write` | Conversations, messages, visitors, tickets and widget sites; `write` also replies, assigns and resolves |
| Payments | `payments:read`, `payments:write` | Payments, transactions, bank connections and e-invoices; `write` also creates payments and issues invoices |
| Products | `products:read`, `products:write` | Products, categories and orders, including an order's payment link |
| CRM | `crm:read`, `crm:write` | Customers, custom fields, merging, CSV export and ZNS |
| Everything | `*` | Full access to every module |

How scopes combine:

- `write` and `send` both include `read`. A key with `email:send` can read the emails it sent.
- `<product>:*` covers every verb of that product, and `*` covers every product.
- Products and CRM grew out of Support: a key with `chat:read`, `chat:write` or `chat:*` keeps working on the Products and CRM endpoints. New keys can use `products:*` and `crm:*` to narrow access.
- A few endpoints belong to the whole business rather than a module. Managing webhook endpoints, reading API **Logs** and reading **Usage** need any `write` scope; managing API keys through the API needs `email:write`.

> **Info: Received mail**
>
> Reading received mail (`/emails/receiving`) needs `email:read` or `email:write` granted on the key. The read access that `email:send` implies is not enough there.

The dashboard offers the scopes of Email, Support and Payments. To create a key with `products:*`, `crm:*` or `*`, call `POST /api-keys` with a key that has `email:write`:

```bash
curl -X POST https://api.hatang.dev/api-keys \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "erp-sync", "scopes": ["products:*", "crm:read"] }'
```

```json
{ "id": "key_123...", "token": "ht_123..." }
```

A key needs the right scope **and** its module must be turned on. A call to a module that is off answers `403 product_disabled` even with the right scope ([Businesses and modules](https://hatang.dev/help/getting-started/modules)).

## Restricted keys {#restricted-keys}

Two optional restrictions limit the damage if a key leaks.

| Restriction | Where to set it | Effect |
|---|---|---|
| **Sending domain** | Dashboard, or `domain_id` on `POST /api-keys` | The key can only send from that verified domain. Sending from another domain fails with `401 restricted_api_key`. Only applies to keys with an Email scope. |
| **Browser origins** | Dashboard: **Where will this key run?** → **In the browser**, then one origin per line, such as `https://shop.vn` | Requests must carry an `Origin` header from the list. Anything else, **including server requests without an `Origin` header**, fails with `401 restricted_api_key`. |

Choose **On your server** (the default) for keys used from your backend; such a key has no origin lock. Choose **In the browser** only for a key that a browser loads. The error message says when a browser-only key was used without an `Origin` header.

## Test keys and the Payments environment

Only the Payments module has environments. Every business has a **development** environment, with a simulated bank and the e-invoice provider's sandbox, and a **production** environment with real money. Payments data never crosses between them. Email, Support, Products and CRM have no environments.

Pick the environment per request with the `Hatang-Environment` header:

```bash
curl https://api.hatang.dev/payments \
  -H "Authorization: Bearer $HATANG_API_KEY" \
  -H "Hatang-Environment: development"
```

| Key | No header | `Hatang-Environment: development` | `Hatang-Environment: production` |
|---|---|---|---|
| `ht_...` | production | development | production |
| `ht_test_...` | development | development | `403 environment_mismatch` |

- Every response repeats the environment it used in the `Hatang-Environment` header.
- Any other header value fails with `422 validation_error`.
- In the Node SDK, pass `environment: "development"` to the `Hatang` constructor, or set `HATANG_ENVIRONMENT`.
- Keys that start with `ht_test_` are development keys from earlier versions of Ha Tang. They can never reach production data. New keys start with `ht_` and use the header.

See [Payments environments](https://hatang.dev/docs/guides/payments/environments.md) for what differs between the two.

## Rotate and revoke keys

Rotate a key when someone who knew it leaves, when it may have leaked, or on a regular schedule:

1. Create a new key with the same scopes and restrictions.
2. Deploy it everywhere the old key was used.
3. Watch the old key's **Last used** column until it stops changing.
4. Click **Revoke** on the old key, or call `DELETE /api-keys/{id}`.

A revoked key stops working within a minute. Revoking cannot be undone; create a new key instead.

## Errors

| Status | `name` | When |
|---|---|---|
| 401 | `missing_api_key` | No `Authorization: Bearer` header |
| 403 | `invalid_api_key` | The key is malformed, unknown or revoked |
| 401 | `restricted_api_key` | The key lacks the scope, is used from an origin it does not allow, or sends from a domain it is not locked to |
| 403 | `product_disabled` | The module behind the endpoint is turned off for the business |
| 403 | `environment_mismatch` | A `ht_test_` key asked for production, or a Payments action does not exist in this environment |
| 422 | `validation_error` | `Hatang-Environment` is not `development` or `production` |

```json
{
  "statusCode": 401,
  "name": "restricted_api_key",
  "message": "This API key does not have the payments:write scope"
}
```

Branch on `name`, not on `message`. The message is for people and may change. None of these errors go away by retrying: fix the key, the scope or the module first. See [Errors](https://hatang.dev/docs/guides/getting-started/errors.md) for the full picture.

## Next steps

- [Errors](https://hatang.dev/docs/guides/getting-started/errors.md)
- [Rate limits](https://hatang.dev/docs/guides/getting-started/rate-limits.md)
- [Security](https://hatang.dev/docs/guides/getting-started/security.md)
- [Payments environments](https://hatang.dev/docs/guides/payments/environments.md)

Source: https://hatang.dev/docs/guides/getting-started/authentication
