Skip to content

REST API Reference

Base URL: https://api.gremia.io

All endpoints require authentication via JWT in the Authorization: Bearer header unless otherwise noted.

Health & Metrics

GET /health

Health check endpoint. No authentication required.

Response:

{
  "status": "healthy",
  "version": "0.1.0"
}

GET /metrics

Prometheus-format metrics. No authentication required.

Response: text/plain; version=0.0.4

gremia_http_requests_total{method="POST",path="/api/v1/chat",status="200"} 42
gremia_http_request_duration_seconds{method="POST",path="/api/v1/chat"} 1.234
gremia_ws_connections_active 3
gremia_ws_connections_total 15

Chat Endpoints

POST /api/v1/chat

Stream an architect conversation response via Server-Sent Events (SSE).

Rate limit: 60 requests/minute

Request body:

{
  "session_id": "uuid-string",
  "messages": [
    { "role": "user", "content": "I need agents for my e-commerce business" }
  ],
  "team_id": "optional-team-uuid"
}
Field Type Required Description
session_id string Yes Unique session identifier
messages array Yes Conversation messages (min 1)
messages[].role string Yes "user" or "assistant"
messages[].content string Yes Message content
team_id string No Team to associate the session with

Response: text/event-stream

data: {"node":"architect","data":{"text":"I'll design a team..."}}

data: {"node":"finalize","data":{"manifest_json":{...}}}

data: [DONE]

Each SSE chunk is a JSON object with:

Field Description
node Current graph node (architect, tools, finalize)
data Node-specific payload

On error:

data: {"error": "Internal processing error"}

Manifest Endpoints

GET /api/v1/manifests

List manifests for the authenticated user.

Query parameters:

Parameter Type Default Description
team_id string User's org Filter by team

Response:

[
  {
    "id": "manifest-uuid",
    "name": "E-Commerce Team",
    "description": "Customer support and inventory",
    "manifest": { "version": "1.0", "agents": [...] },
    "user_id": "user-uuid",
    "org_id": "org-uuid",
    "created_at": "2026-02-15T10:00:00Z"
  }
]

GET /api/v1/manifests/{manifest_id}

Retrieve a single manifest by ID.

Response: Same as list item above.

Errors:

Status Detail
404 Manifest not found

POST /api/v1/manifests

Create a new manifest.

Request body (option A — wrapped):

{
  "name": "My Team",
  "description": "Team description",
  "manifest": {
    "version": "1.0",
    "team_id": "uuid",
    "agents": [...],
    "workflows": [...],
    "mcp_requirements": [...]
  }
}

Request body (option B — raw manifest):

{
  "version": "1.0",
  "team_id": "uuid",
  "agents": [...],
  "workflows": [...],
  "mcp_requirements": [...]
}

Response: 201 Created

{
  "id": "new-manifest-uuid",
  "name": "My Team",
  "manifest": { ... },
  "created_at": "2026-02-15T10:00:00Z"
}

PUT /api/v1/manifests/{manifest_id}

Update an existing manifest. Same body format as POST.

Errors:

Status Detail
400 Invalid manifest format
404 Manifest not found

DELETE /api/v1/manifests/{manifest_id}

Delete a manifest.

Response: 204 No Content


Auth Endpoints

POST /api/v1/auth/verify

Verify a JWT token and return the decoded payload.

Request body:

{
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Response:

{
  "valid": true,
  "payload": {
    "sub": "user-uuid",
    "email": "user@example.com",
    "team_id": "team-uuid",
    "exp": 1739620000
  },
  "error": null
}

On invalid token:

{
  "valid": false,
  "payload": null,
  "error": "invalid token: Signature has expired"
}

Certificate Endpoints

POST /api/v1/certs/sign

Sign a Shell's CSR and return an ephemeral 24-hour certificate.

Rate limit: 10 requests/minute

Request body:

{
  "shell_id": "shell-uuid",
  "csr_pem": "-----BEGIN CERTIFICATE REQUEST-----\n..."
}

Response:

{
  "certificate_pem": "-----BEGIN CERTIFICATE-----\n...",
  "ca_certificate_pem": "-----BEGIN CERTIFICATE-----\n...",
  "valid_hours": 24
}

Errors:

Status Detail
400 Invalid CSR format
500 CA certificates not configured

Execution Endpoints

POST /api/v1/executions

Create a new task execution session.

Request body:

{
  "shell_id": "shell-uuid",
  "manifest_id": "manifest-uuid",
  "task_description": "Generate the weekly sales report"
}

Response:

{
  "execution_id": "exec-uuid",
  "status": "created",
  "task_description": "Generate the weekly sales report",
  "steps_count": 0,
  "final_output": "",
  "error": null
}

GET /api/v1/executions/{execution_id}

Get the current status of an execution.

Response: Same as create response, with updated status and output.

GET /api/v1/executions/{execution_id}/events

Stream execution events via SSE.

Response: text/event-stream

data: {"status": "waiting"}

data: {"status": "completed", "output": "Report generated", "steps": 5}

data: [DONE]

GET /api/v1/executions

List recent executions.

Query parameters:

Parameter Type Default Description
shell_id string null Filter by Shell
limit int 20 Max results

Billing Endpoints

GET /api/v1/billing/plan

Get current plan and credit information.

Response:

{
  "plan": "pro",
  "credits_remaining_cents": 2500,
  "credits_total_cents": 3000,
  "allowed_models": ["fast", "balanced"],
  "period_start": "2026-02-01T00:00:00Z",
  "period_end": "2026-03-01T00:00:00Z"
}

GET /api/v1/billing/usage

Get daily usage history.

Query parameters:

Parameter Type Default Description
days int 30 Number of days to retrieve

Response:

[
  {
    "org_id": "org-uuid",
    "date": "2026-02-15",
    "total_cost_cents": 150,
    "event_count": 12
  }
]

POST /api/v1/billing/checkout

Create a Stripe Checkout session for subscription upgrade.

Request body:

{
  "plan": "pro",
  "success_url": "https://app.gremia.io/billing/success",
  "cancel_url": "https://app.gremia.io/billing/cancel"
}

Response:

{
  "checkout_url": "https://checkout.stripe.com/c/pay_...",
  "session_id": "cs_..."
}

POST /api/v1/billing/portal

Create a Stripe Customer Portal session.

Response:

{
  "portal_url": "https://billing.stripe.com/p/session/..."
}

POST /api/v1/billing/webhook

Process Stripe webhook events. Called by Stripe, not by clients.

Headers required:

Header Description
Stripe-Signature Stripe webhook signature

Privacy Endpoints

POST /api/v1/privacy/consent

Grant consent for a processing purpose.

Request body:

{
  "purpose": "analytics",
  "source": "web",
  "version": "1.0"
}

Response:

{
  "consent_id": "consent-uuid",
  "purpose": "analytics",
  "granted": true,
  "granted_at": "2026-02-15T10:00:00Z",
  "revoked_at": null
}

DELETE /api/v1/privacy/consent/{purpose}

Revoke consent for a specific purpose.

GET /api/v1/privacy/consent

List all consent records for the authenticated user.

POST /api/v1/privacy/erasure

Request erasure of all personal data (GDPR Article 17).

GET /api/v1/privacy/erasure/{request_id}

Check the status of an erasure request.

GET /api/v1/privacy/data-export

Export all personal data (DSAR).

Response:

{
  "user_id": "user-uuid",
  "consent_records": [...],
  "audit_entries": [...],
  "erasure_requests": [...]
}

Admin Endpoints

POST /api/v1/admin/rotate-key

Rotate the encryption key and re-encrypt all sensitive data.

Request body:

{
  "new_key": "64-char-hex-encoded-key"
}

Response:

{
  "rows_re_encrypted": 42,
  "message": "Rotation complete. 42 rows re-encrypted."
}

Errors:

Status Detail
500 Key rotation failed

Common Headers

Request Headers

Header Required Description
Authorization Yes* Bearer {jwt_token}
Content-Type Yes application/json
X-Request-ID No Client-generated request ID for tracing

*Not required for /health and /metrics.

Response Headers

Header Description
X-Request-ID Request tracing ID (echoed or generated)
Content-Encoding gzip for responses > 1KB

Error Format

All errors follow a consistent format:

{
  "detail": "Human-readable error message"
}
Status Code Meaning
400 Bad request (validation error)
401 Unauthorized (invalid/missing JWT)
403 Forbidden (insufficient permissions)
404 Resource not found
422 Unprocessable entity (semantic error)
429 Rate limit exceeded
500 Internal server error