> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.mima.works/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.mima.works/_mcp/server.

# REST API Reference

All API endpoints require an API key passed as a Bearer token:

```
Authorization: Bearer mima_ext_...
```

Base URL: `https://api.mima.works`

Workspace-scoped endpoints use the path prefix `/api/workspaces/{workspace_id}/governance/`.

---

## Authentication

### `GET /api/me`

Resolve workspace and user identity from the API key. Used by the SDK to
auto-resolve `workspace_id`.

**Response**

```json
{
  "user_id": "usr_...",
  "workspace_id": "ws_...",
  "org_id": "org_..."
}
```

---

## GRC Evidence

### `POST /api/workspaces/{workspace_id}/governance/grc/evidence`

Push a single GRC evidence record.

**Request body**

```json
{
  "record_type": "ai_risk_assessment",
  "payload": {
    "risk_level": "limited",
    "risk_summary": "Customer support routing",
    "intended_purpose": "Route customer queries to the correct team",
    "impact_domains": ["customer_service"],
    "art5_self_assessment": true
  },
  "system_name": "customer-support-ai",
  "identity": "alice@example.com",
  "resource": "customer-support-ai",
  "environment": "production",
  "occurred_at": "2026-06-28T09:00:00Z",
  "client_sig": "abc123...",
  "client_sig_algo": "hmac-sha256"
}
```

| Field             | Required | Description                                                             |
| ----------------- | -------- | ----------------------------------------------------------------------- |
| `record_type`     | Yes      | One of the 11 record types                                              |
| `payload`         | Yes      | Record-type-specific payload (see [record-types.md](./record-types.md)) |
| `system_name`     | Yes      | AI system name                                                          |
| `identity`        | No       | Actor email or identifier                                               |
| `resource`        | No       | Resource affected                                                       |
| `environment`     | No       | `production`, `staging`, `development`                                  |
| `occurred_at`     | No       | ISO 8601 timestamp (defaults to ingest time)                            |
| `client_sig`      | No       | HMAC-SHA256 signature (see Signing below)                               |
| `client_sig_algo` | No       | Must be `"hmac-sha256"` when `client_sig` is present                    |

**Dry-run mode**

Append `?dry_run=true` to preview which controls would be earned without writing:

```
POST /api/workspaces/{workspace_id}/governance/grc/evidence?dry_run=true
```

**Response**

```json
{
  "record_id": "rec_...",
  "record_type": "ai_risk_assessment",
  "mapped_controls": ["EUAIA_ART9", "ISO42001_6_1"],
  "detail": "ok"
}
```

Dry-run response — `record_id` is the nil UUID:

```json
{
  "record_id": "00000000-0000-0000-0000-000000000000",
  "record_type": "ai_risk_assessment",
  "mapped_controls": ["EUAIA_ART9", "ISO42001_6_1"],
  "detail": "dry_run"
}
```

---

## Attestations

### `POST /api/workspaces/{workspace_id}/governance/attestations/external`

Push a single attestation record. Used by `mima.attest()` / `mima.wrap()`.

**Request body**

```json
{
  "system_name": "my-pipeline",
  "agent_name": "my-pipeline",
  "tool_name": "generate_report",
  "input_hash": "e3b0c44...",
  "output_hash": "d4e8f23...",
  "schema_version": 2,
  "executed_at": "2026-06-28T09:00:00Z",
  "model_id": "claude-opus-4-6",
  "authorised_by": {
    "identity": "alice@example.com",
    "role": "ml-engineer"
  }
}
```

**Response**

```json
{
  "attestation_id": "att_...",
  "external_verified": true,
  "trust_tier": "witnessed",
  "detail": "ok"
}
```

**Trust tiers**

| Tier        | Meaning                                                    |
| ----------- | ---------------------------------------------------------- |
| `witnessed` | HMAC/Ed25519 signature verified                            |
| `declared`  | No signature — accepted but not cryptographically verified |

**Rate limits**: 2000 requests/minute per workspace. Returns `429` with a `Retry-After` header on excess. The SDK retries once automatically.

---

### `POST /api/workspaces/{workspace_id}/governance/attestations/batch`

Push multiple attestation records in a single request.

**Request body**

```json
{
  "records": [
    { "system_name": "...", "tool_name": "...", "input_hash": "...", "output_hash": "...", "schema_version": 2 },
    { "system_name": "...", "tool_name": "...", "input_hash": "...", "output_hash": "...", "schema_version": 2 }
  ]
}
```

**Response**

```json
{
  "accepted": 2,
  "rejected": 0,
  "results": [
    { "status": "accepted", "attestation_id": "att_..." },
    { "status": "accepted", "attestation_id": "att_..." }
  ]
}
```

---

## Posture

### `GET /api/workspaces/{workspace_id}/governance/grc/posture-brief`

Returns overall posture score and per-framework breakdown.

**Response**

```json
{
  "overall_pct": 74,
  "frameworks": [
    { "framework": "eu_ai_act",   "score_pct": 71 },
    { "framework": "iso_42001",   "score_pct": 79 },
    { "framework": "soc2_type2",  "score_pct": 82 },
    { "framework": "iso_27001",   "score_pct": 65 },
    { "framework": "nist_airf",   "score_pct": 58 }
  ],
  "failing_gates": ["soc2_type2"],
  "unattested_24h": 3
}
```

---

## Systems

### `GET /api/workspaces/{workspace_id}/governance/grc/systems`

List all AI systems.

**Query parameters**: `?name=filter`

**Response**

```json
{
  "systems": [
    {
      "system_id": "sys_...",
      "system_name": "customer-support-ai",
      "registered": true,
      "risk_tier": "limited",
      "last_evidence_at": "2026-06-28T09:00:00Z"
    }
  ]
}
```

---

## Evidence records

### `GET /api/workspaces/{workspace_id}/governance/grc/evidence`

List evidence records.

**Query parameters**

| Parameter     | Description                         |
| ------------- | ----------------------------------- |
| `system_name` | Filter by system                    |
| `record_type` | Filter by type                      |
| `days`        | Lookback window (default: 90)       |
| `limit`       | Max records (default: 50, max: 200) |

**Response**

```json
{
  "records": [
    {
      "record_id": "rec_...",
      "record_type": "model_evaluation",
      "system_name": "loan-scorer",
      "occurred_at": "2026-06-28T09:00:00Z",
      "mapped_controls": ["EUAIA_ART9_4"],
      "source": "sdk",
      "signed": true
    }
  ]
}
```

---

## Gates

### `GET /api/workspaces/{workspace_id}/governance/grc/gates/check`

Returns gate pass/fail status.

**Response**

```json
{
  "gates": [
    {
      "gate_id": "g_quarterly_eval",
      "name": "Quarterly model evaluation",
      "passing": true,
      "blocking": false,
      "exit_code": 0
    },
    {
      "gate_id": "g_risk_assessment",
      "name": "AI risk assessment",
      "passing": false,
      "blocking": true,
      "exit_code": 2,
      "reason": "No ai_risk_assessment record in the last 180 days for loan-scorer"
    }
  ]
}
```

**Exit codes**: `0` = pass, `1` = soft fail, `2` = hard fail (blocking).

---

## Controls

### `POST /api/workspaces/{workspace_id}/governance/grc/derive-controls`

Get recommended record types for a system description.

**Request body**

```json
{
  "description": "A classification model that routes customer support tickets",
  "risk_tier": "limited"
}
```

**Response**

```json
{
  "recommendations": [
    {
      "record_type": "ai_risk_assessment",
      "priority": "required",
      "controls": ["EUAIA_ART9", "ISO42001_6_1"]
    },
    {
      "record_type": "model_evaluation",
      "priority": "required",
      "controls": ["EUAIA_ART9_4", "ISO42001_9_1"]
    }
  ]
}
```

---

## HMAC Signing

GRC records can include a client signature to allow auditors to verify
records were created by a specific SDK instance. You keep the key — Mima never sees it.

### Canonical message format

The canonical message is a compact JSON string with **sorted keys** and **no spaces**:

```json
{"occurred_at":"2026-06-28T09:00:00Z","payload":{...sorted keys...},"record_type":"ai_risk_assessment","system_name":"loan-scorer","workspace_id":"ws_..."}
```

The key order is always alphabetical: `occurred_at` \< `payload` \< `record_type` \< `system_name` \< `workspace_id`.

The `payload` object's keys are also sorted recursively (matching Python's `json.dumps(sort_keys=True, separators=(',', ':'))`).

### Signing

```python
import hashlib, hmac, json

canonical = json.dumps({
    "occurred_at": occurred_at,
    "payload": payload,
    "record_type": record_type,
    "system_name": system_name,
    "workspace_id": workspace_id,
}, sort_keys=True, separators=(",", ":"))

sig = hmac.new(
    bytes.fromhex(signing_key),
    canonical.encode(),
    hashlib.sha256,
).hexdigest()
```

### TypeScript

```typescript
import { createHmac } from 'node:crypto';

// sortedStringify produces the same output as Python's json.dumps(sort_keys=True, separators=(',', ':'))
const canonical = sortedStringify({
  occurred_at: occurredAt,
  payload: payload,
  record_type: recordType,
  system_name: systemName,
  workspace_id: workspaceId,
});

const sig = createHmac('sha256', Buffer.from(signingKey, 'hex'))
  .update(canonical)
  .digest('hex');
```

Both SDKs use the same canonical format and produce identical signatures.

### HMAC vs Ed25519

Mima uses two signing mechanisms for different record types:

| Layer         | Algorithm               | Record type          | What it proves                                                        |
| ------------- | ----------------------- | -------------------- | --------------------------------------------------------------------- |
| `client_sig`  | HMAC-SHA256 (symmetric) | GRC evidence records | Your SDK instance created this record — not Mima, not a third party   |
| `witness_sig` | Ed25519 (asymmetric)    | Attestation records  | Verifiable by anyone with the public key, without needing your secret |

Both use the same `signing_key` you configure in the SDK. For GRC records it's used as an HMAC secret; for attestations it's used as an Ed25519 seed (the SDK derives the keypair and attaches the public verifying key to each record).

### Trust tiers

| `trust_tier` | Meaning                                                     |
| ------------ | ----------------------------------------------------------- |
| `verified`   | Record has a valid cryptographic signature                  |
| `attested`   | Submitted via authenticated SDK/MCP but without signing key |
| `observed`   | Auto-generated by the platform                              |

---

## Error responses

All error responses follow this shape:

```json
{
  "error": "validation_error",
  "message": "record_type is required",
  "field": "record_type"
}
```

| HTTP status | Error code         | Meaning                                     |
| ----------- | ------------------ | ------------------------------------------- |
| `400`       | `bad_request`      | Missing required field or invalid value     |
| `401`       | `unauthorized`     | Invalid or expired API key                  |
| `404`       | `not_found`        | Workspace or system not found               |
| `422`       | `validation_error` | Field-level validation failure              |
| `429`       | `rate_limited`     | Exceeded 2000 rpm; see `Retry-After` header |
| `500`       | `server_error`     | Unexpected server error                     |