Skip to main content

OpenAPI Spec

Eve Horizon's REST API is the single source of truth for all operations. The CLI, web UI, and agent integrations are thin clients that call the API. No client bypasses the API to access the database directly.

The API contract is defined code-first using NestJS controllers and Zod schemas. The generated OpenAPI spec is the authoritative reference for every request and response shape.

API design philosophy

CLI-first

The primary interface for Eve is the CLI, used by humans and AI agents alike. The API is designed to power that CLI experience:

  • CLI-friendly resource hierarchies -- Clear parent-child relationships in URL paths
  • Predictable pagination -- Offset/limit on all list endpoints
  • Consistent errors -- Uniform error shapes across all endpoints
  • Machine-readable output -- Every CLI command supports --json for programmatic consumption

The CLI is thin

The published CLI (@eve/cli) exists solely to:

  1. Parse command-line arguments
  2. Call the appropriate REST endpoint
  3. Format the response for human or machine consumption

The CLI never accesses the database directly, contains business logic, or makes validation decisions. If behavior changes, it changes once in the API.

Authentication

Eve supports multiple authentication methods depending on the client type.

Bearer tokens

All authenticated endpoints require a Bearer token in the Authorization header:

Authorization: Bearer <token>

Tokens are RS256 JWTs issued by the Eve API. Public keys for verification are available at /.well-known/jwks.json.

Authentication methods

MethodUse caseHow to obtain
SSH challengeCLI login via GitHub identityeve login triggers POST /auth/challenge then POST /auth/verify
Supabase exchangeWeb UI loginPOST /auth/exchange converts a Supabase token to an Eve token
Service principalCI/CD and automationCreate via POST /orgs/{org_id}/service-principals, mint tokens via the tokens sub-resource
Admin mintAdmin toolingPOST /auth/mint (admin only)

Token types

Token typeIssued byScope
User tokenSSH challenge or Supabase exchangeFull user permissions based on org/project membership
Service principal tokenService principal token endpointScoped to org with configurable permissions
Job tokenInternal mint (orchestrator)Scoped to a specific job with explicit permission list

Base URL and discovery

EndpointURL
Swagger UIhttp://localhost:4801/docs
OpenAPI JSONhttp://localhost:4801/openapi.json
OpenAPI YAMLhttp://localhost:4801/openapi.yaml
Auth configGET /auth/config
HealthGET /health

The /auth/config endpoint returns the public authentication provider URL and keys so that clients can discover the auth provider dynamically without hardcoding URLs.

REST conventions

HTTP methods

MethodPurposeIdempotent
GETRead resource(s)Yes
POSTCreate resource or trigger actionNo
PUTFull replacementYes
PATCHPartial updateYes
DELETERemove resourceYes

Exception: POST /resource/ensure is idempotent by design (see the ensure pattern below).

Status codes

CodeMeaningWhen used
200 OKSuccessGET, PUT, PATCH, ensure operations
201 CreatedResource createdPOST creating a new resource
204 No ContentSuccess with no bodyDELETE
400 Bad RequestInvalid inputValidation failures, malformed JSON
404 Not FoundResource does not existGET/PUT/DELETE on missing resource
409 ConflictState conflictEnsure with conflicting parameters, gate contention
422 Unprocessable EntitySemantic errorValid JSON but business rule violation
500 Internal Server ErrorServer failureUnexpected errors

URL patterns

# Collection operations
GET /resources # List
POST /resources # Create
POST /resources/ensure # Find-or-create (idempotent)

# Instance operations
GET /resources/{id} # Read
PUT /resources/{id} # Replace
PATCH /resources/{id} # Update
DELETE /resources/{id} # Remove

# Nested resources
GET /resources/{id}/children
POST /resources/{id}/children

The ensure pattern

The /ensure endpoint implements find-or-create semantics that make setup scripts idempotent:

  1. If a resource matching the unique key exists and parameters match, return it (200)
  2. If a resource exists but parameters conflict, return 409
  3. If no resource exists, create it and return 200
# Safe to run multiple times
eve org ensure "My Org"
eve project ensure --name "my-project" --repo-url "https://..."

Versioning

The API does not currently use URL-based versioning (e.g., /v1/). The OpenAPI spec is the contract, and breaking changes are managed through schema evolution. Versioned specs and changelog enforcement in CI are planned.

The canonical spec is checked into the repository under docs/system/openapi.json and docs/system/openapi.yaml. These files are regenerated from the running API:

pnpm --filter @eve/api build
pnpm --filter @eve/api openapi:export

Endpoint categories

The API is organized into the following resource groups.

Auth and identity

Endpoint patternPurpose
/auth/*Login flows (SSH challenge, Supabase exchange, bootstrap)
/.well-known/jwks.jsonPublic keys for JWT verification
/auth/invitesOrg invitation management
/auth/request-accessSelf-service access requests

Organizations

Endpoint patternPurpose
/orgsCRUD for organizations
/orgs/ensureIdempotent org creation
/orgs/{org_id}/membersOrg membership management
/orgs/{org_id}/agentsOrg-level agent listing
/orgs/{org_id}/spendOrg spend and usage tracking

Projects

Endpoint patternPurpose
/projectsCRUD for projects
/projects/ensureIdempotent project creation
/projects/{project_id}/manifestManifest retrieval and validation
/projects/{project_id}/agentsProject agent configuration
/projects/{project_id}/releasesRelease management
/projects/{project_id}/schedulesScheduled trigger management
/projects/{project_id}/routesChat routing configuration

Jobs

Endpoint patternPurpose
/projects/{project_id}/jobsJob CRUD, listing, phase transitions
/projects/{project_id}/jobs/{job_number}/attemptsJob attempt management
/projects/{project_id}/workflowsWorkflow listing and invocation
/jobs/{job_id}/attachmentsJob file attachments
/jobs/{job_id}/receiptJob cost receipt

Secrets

Endpoint patternPurpose
/projects/{project_id}/secretsProject-scoped secrets
/orgs/{org_id}/secretsOrg-scoped secrets
/users/{user_id}/secretsUser-scoped secrets
/system/secretsSystem-level secrets (admin)

Access control

Endpoint patternPurpose
/orgs/{org_id}/service-principalsService principal CRUD and token minting
/orgs/{org_id}/access/canPermission check
/orgs/{org_id}/access/explainPermission resolution explanation
/orgs/{org_id}/access/rolesCustom role management
/orgs/{org_id}/access/bindingsRole binding management
/orgs/{org_id}/access/groupsGroup management

Inference and models

Endpoint patternPurpose
/providersLLM provider listing
/providers/{name}/modelsProvider model discovery
/modelsUnified model listing (BYOK + managed)
/inference/managed-modelsManaged model catalog
/inference/targetsInference target configuration
/inference/v1/chat/completionsOpenAI-compatible inference proxy
/harnessesHarness auth status and capabilities

Webhooks and events

Endpoint patternPurpose
/orgs/{org_id}/webhooksWebhook subscription management
/orgs/{org_id}/webhooks/{wh_id}/deliveriesDelivery history
/orgs/{org_id}/webhooks/{wh_id}/replaysReplay failed deliveries
/orgs/{org_id}/eventsOrg event listing

Analytics and billing

Endpoint patternPurpose
/orgs/{org_id}/analytics/summaryOrg-wide analytics summary
/orgs/{org_id}/analytics/jobsJob analytics
/orgs/{org_id}/analytics/pipelinesPipeline analytics
/orgs/{org_id}/analytics/env-healthEnvironment health metrics
/admin/pricing/*Rate card and exchange rate management
/admin/orgs/{orgId}/usageOrg usage and billing

Threads and chat

Endpoint patternPurpose
/projects/{project_id}/threadsThread listing and management
/orgs/{org_id}/threads/{thread_id}/distillThread distillation

Error format

Errors use NestJS default error shapes with consistent fields:

{
"statusCode": 400,
"message": "Validation failed",
"error": "Bad Request"
}

For gate contention errors, additional context is provided:

{
"statusCode": 409,
"message": "Job blocked on gates",
"blocked_on_gates": ["env:proj_abc123:staging"],
"jobId": "staging-deploy-a3f2dd12"
}
note

A standardized error envelope is planned but not yet enforced. The current format is consistent across endpoints but not formally documented in the OpenAPI spec as a shared schema.

Pagination

All list endpoints return paginated results with offset/limit:

{
"data": [],
"pagination": {
"limit": 10,
"offset": 0,
"count": 50
}
}

Query parameters:

ParameterDefaultDescription
limit10Maximum items to return
offset0Number of items to skip

Results are ordered by created_at descending (newest first).

IDs

Eve uses two ID formats depending on scope:

ScopeFormatExample
Global entities (org, project)TypeIDorg_abc123, proj_xyz789
Scoped entities (job, attempt)Human-friendly numbers123, 1
Cross-context referencesCompositeproj_xxx:123:1

Soft delete

Resources support soft delete via a deleted boolean:

  • Deleted resources are excluded from lists by default
  • Include ?include_deleted=true to show deleted items
  • PATCH with { "deleted": true } marks a resource as deleted

Code-first pipeline

The OpenAPI spec is generated code-first from NestJS controllers and Zod schemas. This ensures that validation rules and API documentation are always in sync.

How it stays in sync

  1. Validation stays in Zod via ZodValidationPipe
  2. OpenAPI schemas are derived from those same Zod schemas via zodSchemaToOpenApi
  3. Controllers add Swagger decorators (@ApiBody, @ApiResponse, @ApiParam)

One schema definition per request/response, used for both runtime validation and OpenAPI generation.

Adding or updating an endpoint

  1. Define or extend the Zod schema in packages/shared/src/schemas
  2. Use ZodValidationPipe in the controller for request validation
  3. Add Swagger decorators that reference the Zod schema via zodSchemaToOpenApi
@Post()
@HttpCode(HttpStatus.CREATED)
@ApiOperation({ summary: 'Create a job in a project' })
@ApiParam({ name: 'project_id', type: String })
@ApiBody({
schema: zodSchemaToOpenApi(CreateJobRequestSchema, 'CreateJobRequest'),
})
@ApiCreatedResponse({
schema: zodSchemaToOpenApi(CreateJobResponseSchema, 'CreateJobResponse'),
})
async create(
@Param('project_id') projectId: string,
@Body(new ZodValidationPipe(CreateJobRequestSchema)) body: CreateJobRequest,
): Promise<CreateJobResponse> {
return this.jobsService.create(projectId, body);
}

Exporting the spec

Generate the canonical spec files:

pnpm --filter @eve/api build
pnpm --filter @eve/api openapi:export

The export sets EVE_OPENAPI_EXPORT=1 and EVE_AUTH_ENABLED=false so it can run without a live API service or database connection.

Service-to-Eve API access

Applications deployed on Eve can access the Eve API using job tokens. The orchestrator mints a scoped token for each job that provides access to project-specific resources.

Job tokens are injected into the execution environment and are scoped to the specific job's permissions. See the Job API reference for details on job token scope and usage.

Service principals provide long-lived API access for CI/CD pipelines and automation. Create a service principal via the API or CLI, mint a token, and use it as a Bearer token:

# Create a service principal for CI
eve service-principal create --org org_xxx --name "ci-pipeline"

# Mint a token
eve service-principal mint-token --org org_xxx --sp sp_yyy

CLI commands

API discovery

# Check API health
eve api health

# Get API version
eve api version

The Eve CLI discovers the API base URL from the EVE_API_URL environment variable or the ~/.eve/config.json file. See eve auth login for initial setup.