← Back to architecture

Public API

contract · build your own surface

Every surface is a client of one contract

Web App, MCP, Slack, Telegram, Mattermost, the extension — all of them are just clients of one public API. Deployed Achilles and want your own surface — a Teams bot, a CLI, a corporate portal — you plug it into the same entry.

Achilles is API-first: every surface, ours and yours, is a client of one public contract. The contract is a curated, stable slice (/public/v1). Our MCP, Slack, Telegram, Mattermost and extension are reference implementations: build the same way. One conversation engine behind the contract →

01

Contract

Outward — a narrow set of operations with the version in the path: the old version lives until a declared retirement of the new one.

  • search findings + sources, read-only
  • ask v2 a full chat turn — a ready answer with sources

OpenAPI is served out of the box (FastAPI) — the client is generated from the schema. Transport is plain REST/JSON; MCP — the same contract wrapped for AI clients.

02

Authorization

We don’t invent our own mechanism — the same entry as at the MCP door.

A key resolves to user_id → role → ACL. A key’s scope only narrows rights — ACL sits on top, there’s no way around it.

03

Identity

The one genuinely new piece — how your surface’s users become Achilles identities. The bridge is identity_mapping with its own source — like 'slack' for Slack or 'telegram' for Telegram.

What the bridge looks like depends on whom the surface represents: two classes below. An account is still created by an admin via invitation — membership in your system grants no access.

04

Governance

A custom surface sits under the same oversight as the native ones — there’s no separate mode.

  • Kill switch and keys. Access closes at once by deactivating the key; the owner creates and revokes keys in their profile, with Owner/Admin oversight on the keys screen.
  • Rate limit and tagging. Every call is rate-limited and tagged to the key — usage is visible on the keys screen; the request text isn’t logged.

Two classes of surfaces

Personal Service
Principle a key = one employee the surface acts for many
Identity key → its user_id own source in identity_mapping, each external → user_id
Access the owner’s personal key a service key + identity resolution on every request
Reference MCP · Extension Slack · Telegram · Mattermost

A service surface goes through the shared “act on behalf of” path — a service key with its own source and presenting the identity on every request. Slack, Telegram and Mattermost are only references for this mechanism, not code to fork: your surface uses the same path without touching the platform.

Our surfaces — reference

Working examples of the contract — build yours by looking at the closest one to your task.

Call contract

request
POST /public/v1/search
Authorization: Bearer ach_…

{
  "query": "release 4.2 status",
  "limit": 8
}
response
200 OK

{
  "results": [
    {
      "title": "RELEASE-482",
      "snippet": "Release 4.2 in regression tests…",
      "source": "ticket",
      "url": "https://jira…/RELEASE-482",
      "score": 0.82
    }
  ],
  "degraded": false
}

Search runs under the key owner’s ACL — what they can’t see won’t appear in the results. Answer synthesis is on the client side; degraded flags a result set without the vector leg (the embedder was silent). v2 will add POST /public/v1/ask — a ready answer with sources in a single call.

API