Hydra logoHydra · Hydra Support

Configuration

Public REST API

Versioned read/write endpoints for leads, contacts, accounts, conversations, and tickets — bearer-authed, rate-limited, ready for Zapier / n8n / custom integrations.

Public REST API (v1)

Hydra exposes a versioned REST API at /api/v1/* for programmatic access to your workspace data. It's the same data your inbox, CRM, and ticket views are built on — same tenant scoping, same role checks, just over HTTP.

Authentication

The public API uses the same bearer-token scheme as Hydra's MCP integration. Mint a key at Settings → API Access, then send it as a Bearer token on every request:

curl -H "Authorization: Bearer hmcp_..." https://hydra-one-tau.vercel.app/api/v1/leads

Keys are tenant-scoped — every request reads and writes within the workspace that minted the key. There's no way for one key to reach another tenant's data.

Scopes

Each key is granted a set of scopes at mint time. The public API enforces them per-route:

ScopeWhat it lets you do
crm:readList and fetch contacts, leads, accounts. Required by all GET endpoints under /leads, /contacts, /accounts.
crm:writeCreate, update, and delete contacts, leads, accounts. Required by POST, PATCH, DELETE.
conversations:readList and fetch conversations. Required by all GET endpoints under /conversations.
conversations:writeUpdate conversations (assign, close). Required by PATCH under /conversations/{id}.
tickets:readList and fetch tickets. Required by all GET endpoints under /tickets.
tickets:writeCreate, update, delete tickets. Required by POST, PATCH, DELETE under /tickets.

A request with an insufficient scope returns 403 forbidden_scope:

{
  "error": "Missing required scope(s): crm:write. Your key has [crm:read].",
  "code": "forbidden_scope"
}

Rate limiting

Every API key is capped at 60 requests per minute, sliding window. Exceeding the cap returns:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0

{
  "error": "Rate limit exceeded. Limit 60 requests per minute per API key.",
  "code": "rate_limited"
}

Successful responses include X-RateLimit-Limit and X-RateLimit-Remaining headers so you can pace your traffic.

The 60/min cap is the v1 default. Per-key custom caps (e.g. higher for production keys, lower for staging keys) ship in a later wave.

Response envelopes

Success

Successful responses return the bare resource (or a list keyed by entity name):

// GET /api/v1/leads
{ "leads": [ { "id": "...", "email": "...", ... } ], "count": 1 }

// GET /api/v1/leads/[id]
{ "lead": { "id": "...", "email": "...", ... } }

// POST /api/v1/leads → 201
{ "lead": { "id": "...", "email": "...", ... } }

// DELETE /api/v1/leads/[id] → 204 No Content

Error

All errors share a single envelope with a stable code field:

{
  "error": "Human-readable message.",
  "code": "validation_error"
}

Stable error codes (branch on these, not on the message text):

CodeHTTPMeaning
unauthorized401Missing, malformed, or revoked bearer token.
forbidden_scope403Token is valid but lacks the required scope.
validation_error400Body or query param failed validation.
not_found404Resource doesn't exist (or doesn't belong to your tenant).
rate_limited429Per-key cap exceeded. Use Retry-After.
server_error500 / 503Internal failure or transient infrastructure issue. Retry safe.

Endpoints

/api/v1/leads

MethodDescription
GET /api/v1/leads?limit=25List recent leads (newest first). limit 1–100, default 25.
POST /api/v1/leadsCreate a lead. Body: { email?, name?, phone?, company?, source? } — at least one field required.
GET /api/v1/leads/{id}Fetch a single lead by id. 404 if not in your tenant.
PATCH /api/v1/leads/{id}Update a lead. Body: any subset of { email, name, phone, company, source }.
DELETE /api/v1/leads/{id}Delete a lead. Cascades through the lead's lifecycle events; the contact's prior conversations remain.

A lead row looks like:

{
  "id": "uuid",
  "tenant_id": "uuid",
  "email": "alice@example.com",
  "name": "Alice Example",
  "phone": null,
  "company": "Example Co",
  "source": "public_api",
  "source_bot_id": null,
  "source_conversation_id": null,
  "created_at": "2026-04-29T..."
}

/api/v1/contacts

Contacts are customers attached to an account. The customers_contact_requires_account DB CHECK means every contact MUST have an account_id — create the account first if needed.

MethodDescription
GET /api/v1/contacts?limit=25&account_id=...List contacts (newest first). Optional account_id filter.
POST /api/v1/contactsCreate a contact. Body requires email and account_id; optional name, phone, company, source.
GET /api/v1/contacts/{id}Fetch a single contact.
PATCH /api/v1/contacts/{id}Update. Subset of { email, name, phone, company, source, account_id }. account_id cannot be set to null on a contact.
DELETE /api/v1/contacts/{id}Delete. The contact's prior conversations remain (FK is ON DELETE SET NULL).

A contact row:

{
  "id": "uuid",
  "tenant_id": "uuid",
  "account_id": "uuid",
  "email": "alice@example.com",
  "name": "Alice Example",
  "phone": null,
  "company": null,
  "source": "public_api",
  "source_bot_id": null,
  "source_conversation_id": null,
  "created_at": "2026-04-29T..."
}

/api/v1/accounts

MethodDescription
GET /api/v1/accounts?limit=25&lifecycle_stage=activeList accounts (newest first). Optional lifecycle_stage filter.
POST /api/v1/accountsCreate. Body requires name; optional domain, industry, website, phone, address, notes, lifecycle_stage, health_score (0–100), renewal_date (YYYY-MM-DD), renewal_status, mrr_cents (integer).
GET /api/v1/accounts/{id}Fetch a single account.
PATCH /api/v1/accounts/{id}Update any subset of the create fields. lifecycle_stage transitions emit lifecycle_events and the account.stage_changed (and account.went_at_risk) flow events.
DELETE /api/v1/accounts/{id}Delete. Returns 409 fk_blocked with a blockers.contacts count if any contact references this account — reassign or delete contacts first. Prefer PATCH lifecycle_stage='churned' for normal lifecycle transitions.

Valid enums:

  • lifecycle_stage: prospect, active, at_risk, churned
  • renewal_status (nullable): upcoming, at_risk, renewed, churned

/api/v1/conversations

Read + targeted-update only. Conversations are created by widget sessions and inbound email — there's no sensible POST shape for partners.

MethodDescription
GET /api/v1/conversations?limit=25&status=open&channel_id=&assigned_agent_id=&customer_id=List (most-recently-updated first). All filters optional.
GET /api/v1/conversations/{id}Fetch one.
PATCH /api/v1/conversations/{id}Update a subset of { status, assigned_agent_id }. status accepts only resolved or closed-equivalent (escalated); reopening or re-assigning is admin-UI-only. Closing emits the same downstream side-effects as the inbox: SLA stamp, summary, CSAT dispatch.

Valid status values exposed via PATCH: resolved, escalated. The full read enum is open | assigned | waiting | resolved | escalated.

Conversation rows include rolled-up tags from the bot pipeline: intent, sentiment, language, plus resolved_at, handoff_at, handoff_reason. These fields are read-only over the public API.

/api/v1/tickets

MethodDescription
GET /api/v1/tickets?limit=25&status=open&priority=high&assigned_to=&customer_id=&conversation_id=List (newest first). All filters optional.
POST /api/v1/ticketsCreate. Body requires title; optional description, priority, customer_id, conversation_id, assigned_to. Cross-tenant references are validated. Sends a confirmation email when the linked customer has an email.
GET /api/v1/tickets/{id}Fetch one.
PATCH /api/v1/tickets/{id}Update any subset of { title, description, status, priority, customer_id, conversation_id, assigned_to }. Emits flow events on status + assignment edges. Status resolved stamps the SLA resolution.
DELETE /api/v1/tickets/{id}Delete.

Valid enums:

  • status: open, in_progress, resolved, closed
  • priority: low, normal, high, urgent

OpenAPI 3.1 spec + interactive docs

  • Spec: https://hydra-one-tau.vercel.app/openapi.yaml — committed in the Hydra repo at public/openapi.yaml. Use it to generate clients (openapi-generator, hey-api, etc.) or import into Postman / Insomnia.
  • Interactive explorer: hydra-help.com/docs/api — Stoplight Elements rendering of the same spec, with try-it-now requests against your tenant key.

How this differs from /api/mcp/v1/*

Hydra also exposes an MCP-shaped surface at /api/mcp/v1/* for AI tool integrations (Claude, Cursor, custom MCP clients). It uses the same bearer keys but a two-step preview/confirm flow for writes — better suited to LLM agents that should ask the user before committing a change.

The public REST API at /api/v1/* is direct: POST creates, PATCH updates, DELETE deletes — no nonce dance. This is the right shape for Zapier, n8n, custom workflows, and any partner integration that's already gating writes upstream.

Both surfaces share the same tenant scoping, the same audit log, and the same rate-limit infrastructure.