# API reference

Every endpoint: what it takes, what it returns, and the request in curl and Python.

Generated from the API's own schema, so it cannot drift from what the server accepts. The base URL is `https://api.feintsecurity.com/v1`, and every request carries an API key as a bearer token; [Authentication](https://feintsecurity.com/docs/authentication) covers keys and scopes, [Errors](https://feintsecurity.com/docs/errors) the two error shapes.

The Python samples assume this setup:

```python
import os
import requests

API = "https://api.feintsecurity.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FEINT_API_KEY']}"}
```

The schema itself is at [`https://api.feintsecurity.com/v1/openapi.json`](https://api.feintsecurity.com/v1/openapi.json) for generators and API clients.

## Canary tokens

Decoy credentials: issue, list, rotate, revoke.

### The canary object

| Field | Type | Description |
|---|---|---|
| `id` | uuid | The canary's id. |
| `provider` | string | The platform the credential lives on: `gitlab` or `aws_iam`. |
| `name` | string | The credential's name at the provider: the token's name at GitLab, or the IAM user's name for an AWS key. Generic on purpose: whoever holds the credential can read it. |
| `placement` | string | Where the secret was placed, as you described it. Named in every alert. |
| `metadata` | object | Your own notes, as given at issue or edited in the console. |
| `scopes` | array of string | The provider's own scopes on the credential, e.g. `create_runner`. Empty for an AWS key, whose user has no permissions at all. |
| `status` | string | `active`, `expired`, `rotated`, `revoked` or `missing`. `expired` is derived from `expires_at`. |
| `secret_prefix` | string | What identifies the secret in a listing: the first 12 characters of a GitLab token, or the whole access key ID of an AWS key, since no part of a secret access key is kept. |
| `secret_fingerprint` | string | SHA-256 of the secret (for an AWS key, of the secret access key alone), as hex. Match a found credential against it without holding the secret. |
| `external_id` | string | The credential's id at the provider: for `aws_iam` the access key ID (`AKIA…`), which is what CloudTrail and GuardDuty show; for `gitlab` the token's numeric id. |
| `access_level` | integer \| null | The provider's access level on the credential, where it has one. 10 is a GitLab Guest; null for an AWS key. |
| `expires_at` | datetime \| null | When the credential stops working: midnight UTC of the expiry date. GitLab enforces it itself; Feint revokes an expired AWS key within minutes. |
| `revoked_at` | datetime \| null | When the canary was revoked or rotated away. Null while active. |
| `rotated_from_id` | uuid \| null | The canary this one replaced, when it was issued by rotation. |
| `created_at` | datetime | When the canary was issued. |
| `last_used_at` | datetime \| null | When the credential was last seen used: GitLab's throttled last-used time, or the latest CloudTrail record for an AWS key. Non-null means the canary has fired. |
| `last_checked_at` | datetime \| null | When Feint's monitor last checked the credential at the provider. |
| `last_used_ips` | array of string | Every source address the provider has reported for the credential, accumulated. |
| `acknowledged_at` | datetime \| null | When someone last acknowledged this canary's use, in the console. |
| `acknowledged_by` | string \| null | The user id of whoever acknowledged it. |
| `needs_attention` | boolean | Used, and not acknowledged since. A fresh use after an acknowledgement sets it again. |
| `client` | object \| null | MSSP partners: the client this canary belongs to. Null for your own organization's. |

```json
{
  "id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
  "provider": "gitlab",
  "name": "ci-3f9a1b2c4d",
  "placement": "prod .env on web-01",
  "metadata": {"owner": "platform"},
  "scopes": ["create_runner"],
  "status": "active",
  "secret_prefix": "glpat-Xy7Q2k",
  "secret_fingerprint": "3c1a9e5f7b2d4a6c8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "external_id": "12345678",
  "access_level": 10,
  "expires_at": "2027-09-26T00:00:00Z",
  "created_at": "2026-09-26T09:14:07.412903Z",
  "last_checked_at": "2026-09-26T09:19:02.113045Z",
  "last_used_ips": [],
  "needs_attention": false
}
```

### POST /v1/tokens — Request a canary token

Requires `tokens:write`.

Mint a decoy credential at the provider and record where you will place it. The response is the only time the secret is returned: Feint keeps a prefix and a fingerprint, not the secret.

**Query parameters**

| Field | Type | Description |
|---|---|---|
| `X-Feint-Client` | string \| null | MSSP partners: act as this client (an id from `GET /v1/clients`). Omit to act as your own organization. |

**Request body**

| Field | Type | Description |
|---|---|---|
| `provider` | string, default "gitlab" | The platform to mint on: `gitlab` (the default) for a project access token, or `aws_iam` for an IAM access key in an AWS account Feint owns. Where AWS decoys are not yet enabled, `aws_iam` is refused with `400`. |
| `placement` | string, required, ≤ 200 characters | Where the secret will live, e.g. `prod .env on web-01`. One line. Every alert names it, so write it for whoever will be reading the alert. |
| `metadata` | object | Your own notes, kept with the canary and shown on alerts. At most 20 entries; keys up to 64 characters, string values up to 500. |
| `ttl_days` | integer \| null, 1–3650 | Lifetime in days. Omit for the default of one year. GitLab enforces the expiry itself; Feint revokes an expired AWS key within minutes of midnight UTC. |

**Returns 201** — a [canary](#the-canary-object), plus `secret`, `secret_notice`.

| Field | Type | Description |
|---|---|---|
| `secret` | string | The credential itself: the GitLab token, or the secret access key of an AWS key, whose access key ID is `external_id`. Returned here and never again. |
| `secret_notice` | string | A reminder, for whoever reads the raw response. |

**Errors**

| Status | Meaning |
|---|---|
| `400` | Refused: the provider would not do it, or a limit was reached. |
| `401` | No valid API key. |
| `403` | The key lacks the scope this endpoint needs. |
| `404` | No canary with that id here, or `X-Feint-Client` names no client of yours. |
| `422` | The request could not be read as this endpoint expects. |
| `429` | Too many requests with this key, or from this address. `Retry-After` says how long to wait. |

```bash
curl -X POST https://api.feintsecurity.com/v1/tokens \
  -H "Authorization: Bearer $FEINT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "placement": "prod .env on web-01",
    "metadata": {"owner": "platform"},
    "ttl_days": 90
  }'
```

```python
r = requests.post(
    f"{API}/tokens",
    headers=HEADERS,
    json={
        "placement": "prod .env on web-01",
        "metadata": {"owner": "platform"},
        "ttl_days": 90,
    },
)
r.raise_for_status()
canary = r.json()
```

Response `201`:

```json
{
  "id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
  "provider": "gitlab",
  "name": "ci-3f9a1b2c4d",
  "placement": "prod .env on web-01",
  "metadata": {"owner": "platform"},
  "scopes": ["create_runner"],
  "status": "active",
  "secret_prefix": "glpat-Xy7Q2k",
  "secret_fingerprint": "3c1a9e5f7b2d4a6c8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "external_id": "12345678",
  "access_level": 10,
  "expires_at": "2027-09-26T00:00:00Z",
  "created_at": "2026-09-26T09:14:07.412903Z",
  "last_checked_at": "2026-09-26T09:19:02.113045Z",
  "last_used_ips": [],
  "needs_attention": false,
  "secret": "glpat-Xy7Q2kN4mP8rT1vW6yB3",
  "secret_notice": "This is the only time the secret is returned. Feint does not store it."
}
```

### GET /v1/tokens — List canary tokens

Requires `tokens:read`.

Every canary the organization owns, retired ones included, with the ones that need attention first.

**Query parameters**

| Field | Type | Description |
|---|---|---|
| `X-Feint-Client` | string \| null | MSSP partners: act as this client (an id from `GET /v1/clients`). Omit to act as your own organization. |

**Returns 200** — an array of [canaries](#the-canary-object).

**Errors**

| Status | Meaning |
|---|---|
| `401` | No valid API key. |
| `403` | The key lacks the scope this endpoint needs. |
| `404` | No canary with that id here, or `X-Feint-Client` names no client of yours. |
| `429` | Too many requests with this key, or from this address. `Retry-After` says how long to wait. |

```bash
curl https://api.feintsecurity.com/v1/tokens \
  -H "Authorization: Bearer $FEINT_API_KEY"
```

```python
r = requests.get(f"{API}/tokens", headers=HEADERS)
r.raise_for_status()
canaries = r.json()
```

Response `200`:

```json
[
  {
    "id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
    "provider": "gitlab",
    "name": "ci-3f9a1b2c4d",
    "placement": "prod .env on web-01",
    "metadata": {"owner": "platform"},
    "scopes": ["create_runner"],
    "status": "active",
    "secret_prefix": "glpat-Xy7Q2k",
    "secret_fingerprint": "3c1a9e5f7b2d4a6c8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
    "external_id": "12345678",
    "access_level": 10,
    "expires_at": "2027-09-26T00:00:00Z",
    "created_at": "2026-09-26T09:14:07.412903Z",
    "last_checked_at": "2026-09-26T09:19:02.113045Z",
    "last_used_ips": [],
    "needs_attention": false
  }
]
```

### GET /v1/tokens/{canary_id} — Get one canary token

Requires `tokens:read`.

One canary by id. Ids are scoped to the organization, so another tenant's id is a 404, not a 403.

**Path parameters**

| Field | Type | Description |
|---|---|---|
| `canary_id` | string, required | The canary's id. |

**Query parameters**

| Field | Type | Description |
|---|---|---|
| `X-Feint-Client` | string \| null | MSSP partners: act as this client (an id from `GET /v1/clients`). Omit to act as your own organization. |

**Returns 200** — a [canary](#the-canary-object).

**Errors**

| Status | Meaning |
|---|---|
| `401` | No valid API key. |
| `403` | The key lacks the scope this endpoint needs. |
| `404` | No canary with that id here, or `X-Feint-Client` names no client of yours. |
| `429` | Too many requests with this key, or from this address. `Retry-After` says how long to wait. |

```bash
curl https://api.feintsecurity.com/v1/tokens/$CANARY_ID \
  -H "Authorization: Bearer $FEINT_API_KEY"
```

```python
canary_id = "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd"

r = requests.get(f"{API}/tokens/{canary_id}", headers=HEADERS)
r.raise_for_status()
canary = r.json()
```

Response `200`:

```json
{
  "id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
  "provider": "gitlab",
  "name": "ci-3f9a1b2c4d",
  "placement": "prod .env on web-01",
  "metadata": {"owner": "platform"},
  "scopes": ["create_runner"],
  "status": "active",
  "secret_prefix": "glpat-Xy7Q2k",
  "secret_fingerprint": "3c1a9e5f7b2d4a6c8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "external_id": "12345678",
  "access_level": 10,
  "expires_at": "2027-09-26T00:00:00Z",
  "created_at": "2026-09-26T09:14:07.412903Z",
  "last_checked_at": "2026-09-26T09:19:02.113045Z",
  "last_used_ips": [],
  "needs_attention": false
}
```

### DELETE /v1/tokens/{canary_id} — Revoke a canary token

Requires `tokens:write`.

Revoke the credential at the provider and mark the canary `revoked`. Revoking twice is harmless. The record stays in the list with its history.

**Path parameters**

| Field | Type | Description |
|---|---|---|
| `canary_id` | string, required | The canary's id. |

**Query parameters**

| Field | Type | Description |
|---|---|---|
| `X-Feint-Client` | string \| null | MSSP partners: act as this client (an id from `GET /v1/clients`). Omit to act as your own organization. |

**Returns 200** — a [canary](#the-canary-object).

**Errors**

| Status | Meaning |
|---|---|
| `400` | Refused: the provider would not do it, or a limit was reached. |
| `401` | No valid API key. |
| `403` | The key lacks the scope this endpoint needs. |
| `404` | No canary with that id here, or `X-Feint-Client` names no client of yours. |
| `429` | Too many requests with this key, or from this address. `Retry-After` says how long to wait. |

```bash
curl -X DELETE https://api.feintsecurity.com/v1/tokens/$CANARY_ID \
  -H "Authorization: Bearer $FEINT_API_KEY"
```

```python
canary_id = "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd"

r = requests.delete(f"{API}/tokens/{canary_id}", headers=HEADERS)
r.raise_for_status()
canary = r.json()
```

Response `200`:

```json
{
  "id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
  "provider": "gitlab",
  "name": "ci-3f9a1b2c4d",
  "placement": "prod .env on web-01",
  "metadata": {"owner": "platform"},
  "scopes": ["create_runner"],
  "status": "active",
  "secret_prefix": "glpat-Xy7Q2k",
  "secret_fingerprint": "3c1a9e5f7b2d4a6c8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "external_id": "12345678",
  "access_level": 10,
  "expires_at": "2027-09-26T00:00:00Z",
  "created_at": "2026-09-26T09:14:07.412903Z",
  "last_checked_at": "2026-09-26T09:19:02.113045Z",
  "last_used_ips": [],
  "needs_attention": false
}
```

### POST /v1/tokens/{canary_id}/rotate — Rotate a canary token

Requires `tokens:write`.

Replace the credential, keeping the placement, metadata and original lifetime. The replacement is minted before the old credential is revoked, so a failure leaves you a working canary. The old record becomes `rotated` and the new one names it in `rotated_from_id`. Rotation does not count against the plan's canary cap.

**Path parameters**

| Field | Type | Description |
|---|---|---|
| `canary_id` | string, required | The canary's id. |

**Query parameters**

| Field | Type | Description |
|---|---|---|
| `X-Feint-Client` | string \| null | MSSP partners: act as this client (an id from `GET /v1/clients`). Omit to act as your own organization. |

**Returns 200** — a [canary](#the-canary-object), plus `secret`, `secret_notice`.

| Field | Type | Description |
|---|---|---|
| `secret` | string | The credential itself: the GitLab token, or the secret access key of an AWS key, whose access key ID is `external_id`. Returned here and never again. |
| `secret_notice` | string | A reminder, for whoever reads the raw response. |

**Errors**

| Status | Meaning |
|---|---|
| `400` | Refused: the provider would not do it, or a limit was reached. |
| `401` | No valid API key. |
| `403` | The key lacks the scope this endpoint needs. |
| `404` | No canary with that id here, or `X-Feint-Client` names no client of yours. |
| `429` | Too many requests with this key, or from this address. `Retry-After` says how long to wait. |

```bash
curl -X POST https://api.feintsecurity.com/v1/tokens/$CANARY_ID/rotate \
  -H "Authorization: Bearer $FEINT_API_KEY"
```

```python
canary_id = "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd"

r = requests.post(f"{API}/tokens/{canary_id}/rotate", headers=HEADERS)
r.raise_for_status()
canary = r.json()
```

Response `200`:

```json
{
  "id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
  "provider": "gitlab",
  "name": "ci-3f9a1b2c4d",
  "placement": "prod .env on web-01",
  "metadata": {"owner": "platform"},
  "scopes": ["create_runner"],
  "status": "active",
  "secret_prefix": "glpat-Xy7Q2k",
  "secret_fingerprint": "3c1a9e5f7b2d4a6c8e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d",
  "external_id": "12345678",
  "access_level": 10,
  "expires_at": "2027-09-26T00:00:00Z",
  "created_at": "2026-09-26T09:14:07.412903Z",
  "last_checked_at": "2026-09-26T09:19:02.113045Z",
  "last_used_ips": [],
  "needs_attention": false,
  "secret": "glpat-Xy7Q2kN4mP8rT1vW6yB3",
  "secret_notice": "This is the only time the secret is returned. Feint does not store it."
}
```

## Alerts

What the monitor has recorded about them.

### The event object

| Field | Type | Description |
|---|---|---|
| `id` | uuid | The event's id. De-duplicate on it. |
| `canary_token_id` | uuid | The canary the event concerns. |
| `kind` | string | `canary.used`, `canary.revoked_externally`, `canary.missing` or `canary.expired`. |
| `occurred_at` | datetime | The provider's own timestamp for the use, where it has one. GitLab reports use at most once per ten minutes or so, so this marks a window, not a single call. For an AWS decoy, the time of the first CloudTrail record in the event's window: later calls within about fifteen minutes join the same event and are counted in `detail`. |
| `detected_at` | datetime | When Feint's monitor saw it. |
| `source_ips` | array of string | Addresses the provider reported for the use. May be empty. |
| `detail` | object | Context for the notification. For `canary.used`: the placement, provider and external id; for AWS decoys also the telemetry CloudTrail gave us for the window: `count` (exact), `calls` (one entry per call, in order, up to the first 50: `occurred_at`, `action` as `service:Operation`, `error` with AWS's error code or null when the call succeeded, `source_ip`, `user_agent`), `first_seen_at`, `last_seen_at`, `records_truncated` and `source` (`cloudtrail`). Every call is at `records_url`. |
| `created_at` | datetime | When the event was written. |
| `records_url` | string | Where to fetch the provider's records behind this event (`GET /v1/alerts/{id}/records`). Empty for providers that have none. |
| `client` | object \| null | MSSP partners: the client whose canary this is. Null for your own organization's. |

```json
{
  "id": "0b6d0f3e-7c1a-4e2b-8f9d-1a2b3c4d5e6f",
  "canary_token_id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
  "kind": "canary.used",
  "occurred_at": "2026-09-26T09:41:00Z",
  "detected_at": "2026-09-26T09:45:12.088311Z",
  "source_ips": ["203.0.113.44"],
  "detail": {
    "external_id": "12345678",
    "placement": "prod .env on web-01",
    "provider": "gitlab"
  },
  "created_at": "2026-09-26T09:45:12.088311Z",
  "records_url": "https://api.feintsecurity.com/v1/alerts/0b6d0f3e-7c1a-4e2b-8f9d-1a2b3c4d5e6f/records"
}
```

### The records object

| Field | Type | Description |
|---|---|---|
| `event_id` | uuid | The event the records belong to. |
| `canary_token_id` | uuid | The canary the event concerns. |
| `count` | integer | How many records are stored for the event. |
| `truncated` | boolean | True when the window held more calls than Feint stores per event (500); the event's `detail.count` is still exact. |
| `records` | array of object | The records, oldest first. |

```json
{
  "event_id": "0b6d0f3e-7c1a-4e2b-8f9d-1a2b3c4d5e6f",
  "canary_token_id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
  "count": 1,
  "truncated": false,
  "records": [
    {
      "occurred_at": "2026-10-03T14:19:13Z",
      "provider_event_id": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
      "record": {
        "awsRegion": "us-east-1",
        "eventID": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
        "eventName": "GetCallerIdentity",
        "eventSource": "sts.amazonaws.com",
        "eventTime": "2026-10-03T14:19:13Z",
        "eventType": "AwsApiCall",
        "readOnly": true,
        "sourceIPAddress": "203.0.113.9",
        "tlsDetails": {
          "cipherSuite": "TLS_AES_128_GCM_SHA256",
          "clientProvidedHostHeader": "sts.us-east-1.amazonaws.com",
          "tlsVersion": "TLSv1.3"
        },
        "userAgent": "aws-cli/2.37.7 md/awscrt#0.27.4",
        "userIdentity": {"accessKeyId": "AKIAEXAMPLEKEY000001"}
      }
    }
  ]
}
```

### GET /v1/alerts — List canary events

Requires `alerts:read`.

Events the monitor has recorded for the organization's canaries, newest `detected_at` first. `limit` is clamped to 1–500. There is no cursor yet: poll, and de-duplicate on `id`.

**Query parameters**

| Field | Type | Description |
|---|---|---|
| `limit` | integer, default 100 | How many events to return, newest first. Values outside 1–500 are clamped. |
| `X-Feint-Client` | string \| null | MSSP partners: act as this client (an id from `GET /v1/clients`). Omit to act as your own organization. |

**Returns 200** — an array of [events](#the-event-object).

**Errors**

| Status | Meaning |
|---|---|
| `401` | No valid API key. |
| `403` | The key lacks the scope this endpoint needs. |
| `404` | No canary with that id here, or `X-Feint-Client` names no client of yours. |
| `422` | The request could not be read as this endpoint expects. |
| `429` | Too many requests with this key, or from this address. `Retry-After` says how long to wait. |

```bash
curl "https://api.feintsecurity.com/v1/alerts?limit=100" \
  -H "Authorization: Bearer $FEINT_API_KEY"
```

```python
r = requests.get(f"{API}/alerts", headers=HEADERS, params={"limit": 100})
r.raise_for_status()
events = r.json()
```

Response `200`:

```json
[
  {
    "id": "0b6d0f3e-7c1a-4e2b-8f9d-1a2b3c4d5e6f",
    "canary_token_id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
    "kind": "canary.used",
    "occurred_at": "2026-09-26T09:41:00Z",
    "detected_at": "2026-09-26T09:45:12.088311Z",
    "source_ips": ["203.0.113.44"],
    "detail": {
      "external_id": "12345678",
      "placement": "prod .env on web-01",
      "provider": "gitlab"
    },
    "created_at": "2026-09-26T09:45:12.088311Z",
    "records_url": "https://api.feintsecurity.com/v1/alerts/0b6d0f3e-7c1a-4e2b-8f9d-1a2b3c4d5e6f/records"
  }
]
```

### GET /v1/alerts/{event_id}/records — Get an event's records

Requires `alerts:read`.

The provider's own records of what was done with the decoy, for incident response. For AWS decoys, every CloudTrail record in the event's window: who called from where, with what tool, which action, its parameters and its result, in CloudTrail's own field names. Each record is reduced to the actor's request and AWS's answer; the rest of what CloudTrail wrote describes Feint's side of the trail and is not served. GitLab events have none, and answer with an empty list. An event outside your organization is a 404.

**Path parameters**

| Field | Type | Description |
|---|---|---|
| `event_id` | string, required | The event's id, from `GET /v1/alerts`. |

**Query parameters**

| Field | Type | Description |
|---|---|---|
| `X-Feint-Client` | string \| null | MSSP partners: act as this client (an id from `GET /v1/clients`). Omit to act as your own organization. |

**Returns 200** — a [records](#the-records-object).

**Errors**

| Status | Meaning |
|---|---|
| `401` | No valid API key. |
| `403` | The key lacks the scope this endpoint needs. |
| `404` | No canary with that id here, or `X-Feint-Client` names no client of yours. |
| `422` | The request could not be read as this endpoint expects. |
| `429` | Too many requests with this key, or from this address. `Retry-After` says how long to wait. |

```bash
curl https://api.feintsecurity.com/v1/alerts/$EVENT_ID/records \
  -H "Authorization: Bearer $FEINT_API_KEY"
```

```python
event_id = "…"

r = requests.get(f"{API}/alerts/{event_id}/records", headers=HEADERS)
r.raise_for_status()
records = r.json()
```

Response `200`:

```json
{
  "event_id": "0b6d0f3e-7c1a-4e2b-8f9d-1a2b3c4d5e6f",
  "canary_token_id": "8d3e6f1a-2b4c-4d5e-9f01-23456789abcd",
  "count": 1,
  "truncated": false,
  "records": [
    {
      "occurred_at": "2026-10-03T14:19:13Z",
      "provider_event_id": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
      "record": {
        "awsRegion": "us-east-1",
        "eventID": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f",
        "eventName": "GetCallerIdentity",
        "eventSource": "sts.amazonaws.com",
        "eventTime": "2026-10-03T14:19:13Z",
        "eventType": "AwsApiCall",
        "readOnly": true,
        "sourceIPAddress": "203.0.113.9",
        "tlsDetails": {
          "cipherSuite": "TLS_AES_128_GCM_SHA256",
          "clientProvidedHostHeader": "sts.us-east-1.amazonaws.com",
          "tlsVersion": "TLSv1.3"
        },
        "userAgent": "aws-cli/2.37.7 md/awscrt#0.27.4",
        "userIdentity": {"accessKeyId": "AKIAEXAMPLEKEY000001"}
      }
    }
  ]
}
```

## Clients

MSSP partners: the clients a key can act as, via `X-Feint-Client`.

### The client object

| Field | Type | Description |
|---|---|---|
| `id` | string | The client's id. Send it as `X-Feint-Client` to act as this client. |
| `name` | string | The client's name, as set in the console. |

```json
{
  "id": "cli_4f1c9a2b7e3d4c5a8b6e0f1d2c3b4a59",
  "name": "Acme Industrial"
}
```

### GET /v1/clients — List your clients

Requires `tokens:read`.

For MSSP partners: the clients your key can act as. Send a client's `id` as the `X-Feint-Client` header on any other endpoint to act as that client; without it, requests act as your own organization. Archived clients are not listed and cannot be acted as.

**Query parameters**

| Field | Type | Description |
|---|---|---|
| `X-Feint-Client` | string \| null | MSSP partners: act as this client (an id from `GET /v1/clients`). Omit to act as your own organization. |

**Returns 200** — an array of [clients](#the-client-object).

**Errors**

| Status | Meaning |
|---|---|
| `401` | No valid API key. |
| `403` | The key lacks the scope this endpoint needs. |
| `429` | Too many requests with this key, or from this address. `Retry-After` says how long to wait. |

```bash
curl https://api.feintsecurity.com/v1/clients \
  -H "Authorization: Bearer $FEINT_API_KEY"
```

```python
r = requests.get(f"{API}/clients", headers=HEADERS)
r.raise_for_status()
clients = r.json()
```

Response `200`:

```json
[
  {
    "id": "cli_4f1c9a2b7e3d4c5a8b6e0f1d2c3b4a59",
    "name": "Acme Industrial"
  }
]
```
