Authentication

Team-scoped API keys, provider credentials, and headless signup flows.

Team API keys

Authenticated endpoints use a bearer token:

Authorization: Bearer <epho_api_key>

An API key belongs to a team rather than an individual user. Every chat lookup, turn lookup, cancellation, and credit read is scoped to that team. A valid key cannot see another team's resources; foreign identifiers return 404.

Create and revoke keys from the Epho application. Plain-text keys are displayed only once when they are minted.

Provider API keys

The provider_api_key in a run request is not the same credential as the Epho bearer token.

Epho derives the provider from the selected harness and model, then passes the opaque key to that provider through the sandbox runner:

Harness and modelKey is routed to
codex + gpt-*OpenAI
claude + claude-*Anthropic
opencode + openai/*OpenAI
opencode + anthropic/*Anthropic
opencode + opencode/*OpenCode Zen

Supply the provider key on every paid run, including resumed chats. Free OpenCode Zen models ending in -free are the only runs that do not require one.

Headless email signup

Clients without an existing account can create one through:

POST /api/v1/signup
Content-Type: application/json
{
  "username": "[email protected]",
  "password": "a-strong-password"
}

username must be an email address. A successful request creates a user, personal team, and team-scoped key:

{
  "api_key": "1|..."
}

The endpoint returns 201, shows the key once, and is limited to three requests per IP per minute.

Device OAuth signup

Headless clients can also use Google or GitHub:

POST /api/v1/signup/google

The 201 response includes an authorization_url, ticket, poll_secret, poll_url, and expires_in. The ticket lives for 600 seconds.

  1. Open authorization_url in the user's browser.
  2. Poll poll_url with the secret:
    X-Device-Secret: <poll_secret>
    
  3. Continue while the response is {"status":"pending"}.
  4. A completed response returns the API key and consumes the ticket:
    {
      "status": "complete",
      "api_key": "1|...",
      "is_new_user": true
    }
    

The poll secret may alternatively be provided as the secret query parameter. The API key cannot be retrieved from the same ticket twice.