# Authentication

Every request is authenticated with a **workspace API key**, sent as a bearer token:

```
Authorization: Bearer maetra_xxxxxxxxxxxxxxxxxxxx
```

Keys start with `maetra_`. A key belongs to one workspace and carries a fixed set of **scopes**. The same key authenticates the [REST API](https://maetra.io/docs/getting-started/quickstart) and the [MCP server](https://maetra.io/docs/mcp-server/overview-and-connection).

### Create an API key (in the dashboard)

1. Open the Maetra dashboard and go to **Settings → API keys**.
2. Click **Create API key**.
3. Fill in the dialog:
   * **Key name** — a label to recognise it later, e.g. `Production FinanceAgent`.
   * **Access scopes** — choose **Full access**, or **Custom scopes** to pick per module (Discover, Comply, Govern, Secure, Audit, Task Guard). Grant the least a key needs — see [Scopes](#scopes) below.
   * **IP restrictions** *(optional)* — one or more IPs/CIDR ranges the key may be used from, e.g. `192.168.1.0/24`.
4. Click **Create**. The full key is shown **once** — hit **Copy key**, store it in a secret manager, then **I've saved my key**.

> **Important**
> Maetra stores only a hash of the key and can never show it again. If a key is lost or leaked, **revoke it and create a new one** — you can't recover it.


### Scopes

Scopes are checked per request. A call returns `403 Forbidden` if the key is valid but lacks the required scope.

| Scope                      | Grants                         |
| -------------------------- | ------------------------------ |
| `govern:checkpoints:write` | Create checkpoints             |
| `govern:checkpoints:read`  | Read / long-poll checkpoints   |
| `govern:policies:read`     | List active policies           |
| `secure:scan:write`        | Scan content                   |
| `secure:rules:read`        | List Secure rules              |
| `secure:rules:write`       | Create and update Secure rules |
| `secure:incidents:read`    | List incidents                 |
| `discover:agents:read`     | List registered agents         |
| `task_guard:tasks:write`   | Start, update, pause, resume, cancel, and complete Task Guard tasks |
| `task_guard:tasks:read`    | Retrieve active Task Guard task context |
| `task_guard:checks:write`  | Check action alignment and explain task relationships |
| `task_guard:effects:write` | Report and compare actual action effects |
| `task_guard:confirmations:write` | Submit trusted host user events and scope confirmations |

#### Wildcards

A key may hold wildcard scopes: `*` (everything), or a module prefix like `govern:*` / `secure:*` / `discover:*` / `task_guard:*`. The dashboard's **Full access** grants `*`; picking a whole module under **Custom scopes** grants that module's `*`.

> **Note**
> `task_guard:confirmations:write` is necessary but not sufficient for trusted host events and inline scope confirmations. The API key must also be marked as a trusted Task Guard confirmation credential for the workspace.


### Check a key

Use `GET /v1/ping` to confirm a key is valid and see its scopes:

#### cURL

```bash
curl "https://api.maetra.io/v1/ping" \
  -H "Authorization: Bearer $MAETRA_API_KEY"
```

#### JavaScript

```javascript
const res = await fetch("https://api.maetra.io/v1/ping", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.MAETRA_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Maetra API ${res.status}`);
const data = await res.json();
console.log(data);
```

#### TypeScript

```typescript
const res = await fetch("https://api.maetra.io/v1/ping", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.MAETRA_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Maetra API ${res.status}`);
const data = (await res.json());
```

#### Python

```python
import os, requests

res = requests.get(
    "https://api.maetra.io/v1/ping",
    headers={"Authorization": f"Bearer {os.environ['MAETRA_API_KEY']}"},
)
res.raise_for_status()
print(res.json())
```

#### Rust

```rust
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("MAETRA_API_KEY")?;
    let client = reqwest::Client::new();
    let res = client
        .get("https://api.maetra.io/v1/ping")
        .bearer_auth(&key)
        .send()
        .await?;
    let data: serde_json::Value = res.json().await?;
    println!("{data:#}");
    Ok(())
}
```

#### C++

```cpp
#include <curl/curl.h>
#include <cstdlib>
#include <string>

int main() {
    CURL* curl = curl_easy_init();
    std::string auth = "Authorization: Bearer " + std::string(std::getenv("MAETRA_API_KEY"));
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, auth.c_str());
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.maetra.io/v1/ping");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_perform(curl);   // response is written to stdout by default
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}
```

#### Java

```java
import java.net.URI;
import java.net.http.*;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.maetra.io/v1/ping"))
    .header("Authorization", "Bearer " + System.getenv("MAETRA_API_KEY"))
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```


```json
{ "ok": true, "workspace_id": "ws_3Nf9k2", "scopes": ["govern:checkpoints:write"] }
```

### Revoke a key

Revoke a key from **Settings → API keys** (the row's actions menu). Revocation is immediate — further requests return `401 Unauthorized`. Revoking one key never affects the others.

See [Errors & rate limits](https://maetra.io/docs/getting-started/errors-and-rate-limits) for the full error format.