# Authentication

> API reference · Wathiqaty Insurer API v1 (draft)

Exchange your API client's credentials for a 15-minute bearer token (OAuth 2.0 client credentials, RFC 6749 §4.4). Cache it and renew it shortly before it expires.

Base URL: `https://sandbox-api.wathiqaty.com/api/v1` (sandbox), `https://api.wathiqaty.com/api/v1` (production).

- Get an access token: `POST /auth/partner/token`

## Get an access token

`POST /auth/partner/token`

- Auth: HTTP Basic (`client_id:client_secret`)

Send the client id and secret with HTTP Basic auth. There is no refresh token: ask again with the same credentials. This route is limited to 10 calls a minute per client.

### Body (form-encoded)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `grant_type` | string | yes | Always `client_credentials`. One of: `client_credentials`. |
| `scope` | string | no | Space-separated subset of the client's scopes. Omit it to get them all. |

### Request examples

```bash
curl -X POST "https://sandbox-api.wathiqaty.com/api/v1/auth/partner/token" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials&scope=policies%3Aread+claims%3Aread+claims%3Awrite"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/auth/partner/token", {
  method: "POST",
  headers: {
    Authorization: "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64"),
    "Content-Type": "application/x-www-form-urlencoded",
  },
  body: new URLSearchParams({
  "grant_type": "client_credentials",
  "scope": "policies:read claims:read claims:write"
}),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.post(
    "https://sandbox-api.wathiqaty.com/api/v1/auth/partner/token",
    auth=(client_id, client_secret),
    headers={
        "Content-Type": "application/x-www-form-urlencoded",
    },
    data={
  "grant_type": "client_credentials",
  "scope": "policies:read claims:read claims:write"
},
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Post,
    "https://sandbox-api.wathiqaty.com/api/v1/auth/partner/token");
req.Headers.Authorization = new AuthenticationHeaderValue("Basic",
    Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")));
req.Content = new FormUrlEncodedContent(new Dictionary<string, string> {
    ["grant_type"] = "client_credentials",
    ["scope"] = "policies:read claims:read claims:write",
});
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

A token.

```json
{
  "status": "success",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
    "tokenType": "Bearer",
    "expiresIn": 900,
    "scope": "policies:read claims:read claims:write"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/auth/partner/token",
    "method": "POST",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 400 | `invalid_scope` | A requested scope isn't granted to this client. |
| 401 | `invalid_client` | Unknown client, wrong secret, or the client is revoked. |
| 403 | `ip_not_allowed` | The client has an IP allow-list and this IP isn't on it. |
| 403 | `tenant_not_active` | Your company is suspended on Wathiqaty. |
| 429 | `rate_limited` | More than 10 token requests a minute. Cache tokens. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).
