---
name: isodora-api
description: >
  Call Isodora's public REST API (/api/v1). Use for curl, fetch, n8n HTTP,
  Grafana/Power BI datasources, scripts, CI, or any non-MCP integration.
  Covers base URLs, Bearer isk_u_ auth, response envelope, cursor pagination,
  error codes, and grepping the live public OpenAPI. Prefer this over inventing
  paths. For agent hosts (Cursor, Claude Code) prefer isodora-mcp.
metadata:
  author: isodora
  version: "1.0.0"
  parent: isodora
---

# Isodora public API (`/api/v1`)

Authenticated HTTP surface for integrations. Same authorization pipeline as MCP.

- Base EU: `https://app.isodora.se/api/v1`
- Base US: `https://app.isodora.app/api/v1`
- Auth: `Authorization: Bearer isk_u_…` (also accepts platform keys / session JWT for some clients — customers use personal keys)

**Prerequisite:** complete `isodora-keys` first if no key.

---

## Live contract — do not invent endpoints

```bash
curl -sS https://api-docs.isodora.se/openapi.public.json | head
# Grep paths:
curl -sS https://api-docs.isodora.se/openapi.public.json | grep -o '"\/api\/v1[^"]*"' | sort -u
```

Human quickstart: portal docs / `docs/api/public-api-v1.md`. Cached endpoint list: [references/endpoints.md](references/endpoints.md) — verify against the live JSON when unsure.

---

## First call

```bash
curl -sS -H "Authorization: Bearer $ISODORA_API_KEY" \
  "$ISODORA_BASE_URL/api/v1/me"
```

Success:

```json
{ "data": { … }, "meta": { "next_cursor": null, "total": 1 }, "request_id": "…" }
```

---

## Conventions

- **Pagination:** `?cursor=&limit=` (max 200). Pass `meta.next_cursor` back as `cursor`.
- **Rate limits:** 120 req/min per key, 600/min per user; headers `X-RateLimit-*`; 429 + `Retry-After`. Semantic document search: 20/min (spends AI credits).
- **Spend gate:** AI-costing routes may return 429 with `details.code` in `credits_depleted | daily_cap_reached | import_quota | …`.
- **Writes:** PATCH gap/task status requires a **Read & write** key.

---

## Typical flows

1. `GET /me` → discover orgs
2. `GET /organizations` → pick `orgId`
3. `GET /organizations/{orgId}/compliance/overview?project_id=`
4. `GET /organizations/{orgId}/gaps?status=open&severity=high`
5. Optional write: `PATCH /organizations/{orgId}/tasks/{taskId}` with `{ "status": "…" }`

QMS-only: `/risks`, `/suppliers` → 403 `forbidden_scope` without the module.

---

## Do not

- Call internal UI routes under `/api/` outside `/api/v1` and `/api/mcp`
- Hardcode org/project UUIDs from examples
- Retry-storm on 429 without respecting `Retry-After`
