Skip to main content

Admin API

The Admin API provides RESTful endpoints for managing OpenPact through the Admin UI or programmatically. All endpoints (except setup and login) require authentication.

Base URL

http://localhost:8080/api

Authentication

The Admin API uses a two-token authentication system:

TokenStorageLifetimePurpose
Refresh TokenHTTP-only cookie3 daysObtain new access tokens
Access TokenIn-memory (JS)15 minutesAPI authorization

Authentication Flow

1. POST /api/auth/login     → Receive refresh token cookie
2. GET /api/session → Exchange cookie for access token
3. GET /api/scripts → Use access token in Authorization header
4. (token expires) → Automatically refresh via /api/session

Setup Endpoints

These endpoints are only available before first-run setup is complete.

GET /api/setup/status

Check if first-run setup is required.

Response (Setup Required):

{
"setup_required": true
}

Response (Setup Complete):

{
"setup_required": false
}

POST /api/setup

Complete first-run setup by creating the admin user.

Request:

{
"username": "admin",
"password": "your-secure-password",
"confirm_password": "your-secure-password"
}

Password Requirements:

  • Option 1: 16+ characters (passphrase style)
  • Option 2: 12+ characters with 3 of 4: uppercase, lowercase, number, symbol

Response (Success):

{
"success": true,
"message": "Setup complete. Please log in."
}

Errors:

StatusCodeDescription
400password_invalidPassword doesn't meet requirements
400password_mismatchPasswords don't match
403setup_completedSetup already completed

Authentication Endpoints

POST /api/auth/login

Authenticate and receive a refresh token cookie.

Request:

{
"username": "admin",
"password": "your-password"
}

Response (Success):

{
"message": "Login successful"
}

Response Headers:

Set-Cookie: refresh=xxx; HttpOnly; Secure; Path=/api/session; SameSite=Strict; Max-Age=259200

Errors:

StatusCodeDescription
401invalid_credentialsUsername or password incorrect
429rate_limitedToo many login attempts (5/minute)

GET /api/session

Exchange refresh token cookie for an access token.

Request:

Requires refresh cookie (sent automatically by browser).

Response (Success):

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2024-01-15T10:45:00Z",
"username": "admin"
}

Errors:

StatusCodeDescription
401no_refresh_tokenNo refresh token cookie present
401invalid_refresh_tokenToken expired or invalid

POST /api/auth/logout

Clear the refresh token cookie.

Response:

204 No Content

Response Headers:

Set-Cookie: refresh=; Max-Age=-1; HttpOnly; Secure; Path=/api/session; SameSite=Strict

GET /api/auth/me

Get current user information.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"username": "admin",
"role": "admin"
}

Script Endpoints

All script endpoints require authentication via Bearer token.

GET /api/scripts

List all scripts with their status.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"scripts": [
{
"name": "weather.star",
"path": "scripts/weather.star",
"hash": "sha256:abc123def456...",
"status": "approved",
"description": "Get current weather for a city",
"required_secrets": ["WEATHER_API_KEY"],
"approved_at": "2024-01-15T10:30:00Z",
"approved_by": "admin",
"created_at": "2024-01-15T09:00:00Z",
"modified_at": "2024-01-15T10:00:00Z"
},
{
"name": "new_feature.star",
"path": "scripts/new_feature.star",
"hash": "sha256:789ghi012jkl...",
"status": "pending",
"description": "Experimental feature",
"required_secrets": [],
"created_at": "2024-01-15T11:00:00Z",
"modified_at": "2024-01-15T11:00:00Z"
}
]
}

Script Status Values:

StatusDescription
pendingAwaiting admin review
approvedCan be executed
rejectedBlocked from execution

GET /api/scripts/:name

Get detailed information about a specific script.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"name": "weather.star",
"source": "# @description: Get current weather for a city\n# @secrets: WEATHER_API_KEY\n\ndef get_weather(city):\n ...",
"hash": "sha256:abc123def456...",
"status": "approved",
"metadata": {
"description": "Get current weather for a city",
"author": "admin",
"version": "1.0.0",
"secrets": ["WEATHER_API_KEY"]
},
"execution_history": [
{
"timestamp": "2024-01-15T14:30:00Z",
"success": true,
"duration_ms": 150
},
{
"timestamp": "2024-01-15T14:00:00Z",
"success": true,
"duration_ms": 145
}
]
}

POST /api/scripts

Create a new script.

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Request:

{
"name": "new_script.star",
"source": "# @description: My new script\n\ndef main():\n return {\"message\": \"Hello\"}"
}

Response:

{
"name": "new_script.star",
"status": "pending",
"hash": "sha256:newscripthash..."
}

Note: New scripts always start with pending status.

PUT /api/scripts/:name

Update an existing script.

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Request:

{
"source": "# @description: Updated script\n\ndef main():\n return {\"message\": \"Updated\"}"
}

Response:

{
"name": "existing_script.star",
"status": "pending",
"hash": "sha256:newupdatedhash..."
}

Note: Editing a script resets its status to pending (requires re-approval).

DELETE /api/scripts/:name

Delete a script.

Request Headers:

Authorization: Bearer <access_token>

Response:

204 No Content

POST /api/scripts/:name/approve

Approve a script for execution.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"name": "weather.star",
"status": "approved",
"approved_at": "2024-01-15T15:00:00Z",
"approved_by": "admin"
}

POST /api/scripts/:name/reject

Reject a script (blocks execution).

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Request:

{
"reason": "Script accesses unauthorized external API"
}

Response:

{
"name": "unsafe_script.star",
"status": "rejected",
"rejected_at": "2024-01-15T15:00:00Z",
"rejected_by": "admin",
"reason": "Script accesses unauthorized external API"
}

POST /api/scripts/:name/test

Test-run an approved script.

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Request:

{
"args": {
"city": "London"
}
}

Response (Success):

{
"success": true,
"result": {
"city": "London",
"temp_c": 15.5,
"condition": "Partly cloudy"
},
"duration_ms": 150,
"logs": [
"Fetching weather for London",
"API request successful"
]
}

Response (Failure):

{
"success": false,
"error": "HTTP request failed: connection timeout",
"duration_ms": 30000,
"logs": [
"Fetching weather for London",
"API request timed out"
]
}

Note: Only approved scripts can be tested via this endpoint.


Version History Endpoints

GET /api/scripts/:name/history

Get version history for a script.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"versions": [
{
"commit": "abc123def456...",
"message": "Update weather.star via admin UI",
"author": "admin",
"timestamp": "2024-01-15T14:30:00Z"
},
{
"commit": "789ghi012jkl...",
"message": "Create weather.star via admin UI",
"author": "admin",
"timestamp": "2024-01-15T10:00:00Z"
}
]
}

GET /api/scripts/:name/history/:commit

Get script source at a specific version.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"commit": "789ghi012jkl...",
"source": "# @description: Original weather script\n..."
}

GET /api/scripts/:name/diff

Get diff between two versions.

Query Parameters:

ParameterTypeDescription
fromstringStarting commit hash
tostringEnding commit hash

Request:

GET /api/scripts/weather.star/diff?from=789ghi012jkl&to=abc123def456

Response:

{
"diff": "@@ -15,7 +15,7 @@ def get_weather(city):\n url = format(\n- \"https://api.v1...\",\n+ \"https://api.v2...\","
}

POST /api/scripts/:name/restore/:commit

Restore a script to a previous version.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"name": "weather.star",
"status": "pending",
"restored_from": "789ghi012jkl..."
}

Note: Restoring creates a new commit and resets status to pending.


Secrets Endpoints

Secret values are never returned via the API. Only metadata is provided.

GET /api/secrets

List all configured secrets.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"secrets": [
{
"name": "WEATHER_API_KEY",
"set": true,
"last_updated": "2024-01-15T10:00:00Z"
},
{
"name": "GITHUB_TOKEN",
"set": true,
"last_updated": "2024-01-14T09:00:00Z"
},
{
"name": "SLACK_WEBHOOK",
"set": false,
"last_updated": null
}
]
}

POST /api/secrets/:name

Set a secret value.

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Request:

{
"value": "sk-your-api-key-here"
}

Response:

{
"name": "WEATHER_API_KEY",
"set": true
}

Note: The actual secret value is never returned.

DELETE /api/secrets/:name

Remove a secret.

Request Headers:

Authorization: Bearer <access_token>

Response:

204 No Content

Session Endpoints

Session endpoints manage AI conversation sessions. OpenPact proxies these to the OpenCode server, which stores all session data internally. See the OpenCode server documentation for details on the underlying API.

GET /api/sessions

List all sessions with active status.

Request Headers:

Authorization: Bearer <access_token>

Response:

[
{
"id": "ses_379f7b1c2ffekEa2VDmHmviVwS",
"slug": "misty-eagle",
"title": "Debugging the API endpoint",
"directory": "/workspace",
"version": "1.2.6",
"time": {
"created": 1771775217213,
"updated": 1771775221546
},
"active": true
}
]

The active field indicates which session currently receives Discord messages.

POST /api/sessions

Create a new session and set it as active.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"id": "ses_new123abc",
"slug": "bright-fox",
"title": "",
"time": {
"created": 1771775300000,
"updated": 1771775300000
}
}

GET /api/sessions/:id

Get details for a specific session.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"id": "ses_379f7b1c2ffekEa2VDmHmviVwS",
"slug": "misty-eagle",
"title": "Debugging the API endpoint",
"directory": "/workspace",
"version": "1.2.6",
"time": {
"created": 1771775217213,
"updated": 1771775221546
}
}

DELETE /api/sessions/:id

Delete a session and all its messages.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"ok": true
}

GET /api/sessions/:id/messages

Get message history for a session.

Query Parameters:

ParameterTypeDefaultDescription
limitinteger50Maximum number of messages to return

Request Headers:

Authorization: Bearer <access_token>

Response:

[
{
"id": "msg_abc123",
"sessionID": "ses_379f7b1c2ffekEa2VDmHmviVwS",
"role": "user",
"parts": [
{ "type": "text", "text": "Hello, can you help me?" }
],
"time": {
"created": 1771775220000,
"updated": 1771775220000
}
}
]

WS /api/sessions/:id/chat

WebSocket endpoint for real-time chat within a session. Authenticates via query parameter since browsers cannot set headers on WebSocket upgrades.

Connection:

ws://localhost:8080/api/sessions/:id/chat?token=<access_token>

Client → Server:

{
"type": "message",
"content": "Hello, what can you help me with?"
}

Server → Client:

{ "type": "connected", "session_id": "ses_abc123" }
{ "type": "text", "content": "I can help you with..." }
{ "type": "done" }
{ "type": "error", "content": "Engine error: ..." }

Messages are streamed incrementally as text events, followed by a done event when the response is complete.


Model Endpoints

Model endpoints allow viewing available AI models and changing the default model used for new sessions. The preference is persisted to disk and survives restarts.

GET /api/models

List all available models and the current default.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"models": [
{
"provider_id": "anthropic",
"model_id": "claude-sonnet-4-20250514",
"context_limit": 200000,
"output_limit": 16000
},
{
"provider_id": "anthropic",
"model_id": "claude-opus-4-20250514",
"context_limit": 200000,
"output_limit": 32000
},
{
"provider_id": "openai",
"model_id": "gpt-4o",
"context_limit": 128000,
"output_limit": 16384
}
],
"default": {
"provider": "anthropic",
"model": "claude-sonnet-4-20250514"
}
}

PUT /api/models/default

Set the default model for new sessions.

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Request:

{
"provider": "anthropic",
"model": "claude-opus-4-20250514"
}

Response:

{
"ok": true,
"default": {
"provider": "anthropic",
"model": "claude-opus-4-20250514"
}
}

Errors:

StatusDescription
400model field is missing
note

Changing the default model only affects new sessions. Existing sessions continue using the model they were started with.


Schedule Endpoints

Schedule endpoints manage cron-based scheduled jobs. All endpoints require authentication via Bearer token. Mutations automatically trigger a scheduler reload.

GET /api/schedules

List all schedules.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"schedules": [
{
"id": "a1b2c3d4e5f6g7h8",
"name": "Daily report",
"cron_expr": "0 9 * * 1-5",
"type": "script",
"enabled": true,
"script_name": "daily_report.star",
"output_target": {
"provider": "discord",
"channel_id": "channel:123456789"
},
"created_at": "2026-03-01T12:00:00Z",
"updated_at": "2026-03-01T12:00:00Z",
"last_run_at": "2026-03-01T09:00:00Z",
"last_run_status": "success",
"last_run_output": "Report generated successfully"
}
]
}

POST /api/schedules

Create a new schedule.

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Request:

{
"name": "Daily report",
"cron_expr": "0 9 * * 1-5",
"type": "script",
"enabled": true,
"script_name": "daily_report.star",
"output_target": {
"provider": "discord",
"channel_id": "channel:123456789"
}
}

Required fields: name, cron_expr, type

Type-specific fields:

  • type: "script" requires script_name
  • type: "agent" requires prompt

Optional fields:

  • run_once (boolean) — If true, the schedule auto-disables after one execution

Response (201 Created):

{
"id": "a1b2c3d4e5f6g7h8",
"name": "Daily report",
"cron_expr": "0 9 * * 1-5",
"type": "script",
"enabled": true,
"script_name": "daily_report.star",
"output_target": {
"provider": "discord",
"channel_id": "channel:123456789"
},
"created_at": "2026-03-01T12:00:00Z",
"updated_at": "2026-03-01T12:00:00Z"
}

Errors:

StatusDescription
400Invalid JSON, missing required fields, or invalid cron expression

GET /api/schedules/:id

Get a single schedule by ID.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"id": "a1b2c3d4e5f6g7h8",
"name": "Daily report",
"cron_expr": "0 9 * * 1-5",
"type": "script",
"enabled": true,
"script_name": "daily_report.star",
"output_target": {
"provider": "discord",
"channel_id": "channel:123456789"
},
"created_at": "2026-03-01T12:00:00Z",
"updated_at": "2026-03-01T12:00:00Z",
"last_run_at": "2026-03-01T09:00:00Z",
"last_run_status": "success",
"last_run_error": "",
"last_run_output": "Report generated successfully"
}

Errors:

StatusDescription
404Schedule not found

PUT /api/schedules/:id

Update an existing schedule. Only provided fields are updated.

Request Headers:

Authorization: Bearer <access_token>
Content-Type: application/json

Request:

{
"name": "Morning report",
"cron_expr": "0 10 * * 1-5"
}

Response:

{
"id": "a1b2c3d4e5f6g7h8",
"name": "Morning report",
"cron_expr": "0 10 * * 1-5",
"type": "script",
"enabled": true,
"script_name": "daily_report.star",
"created_at": "2026-03-01T12:00:00Z",
"updated_at": "2026-03-01T14:00:00Z"
}

Errors:

StatusDescription
400Invalid cron expression or invalid field values
404Schedule not found

DELETE /api/schedules/:id

Delete a schedule.

Request Headers:

Authorization: Bearer <access_token>

Response:

204 No Content

Errors:

StatusDescription
404Schedule not found

POST /api/schedules/:id/enable

Enable a schedule.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"status": "enabled"
}

Errors:

StatusDescription
404Schedule not found

POST /api/schedules/:id/disable

Disable a schedule. The job stops running but its configuration is preserved.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"status": "disabled"
}

Errors:

StatusDescription
404Schedule not found

POST /api/schedules/:id/run

Trigger an immediate run of a schedule. The job executes in a background goroutine.

Request Headers:

Authorization: Bearer <access_token>

Response:

{
"status": "triggered"
}

Errors:

StatusDescription
400Invalid schedule or execution error
404Schedule not found
503Scheduler not available
note

The run endpoint triggers the job asynchronously. The response confirms the job was triggered, not that it completed. Check the schedule's last_run_* fields for the result.


Error Responses

All error responses follow a consistent format:

{
"error": "error_code",
"message": "Human-readable error description"
}

Common Error Codes

HTTP StatusError CodeDescription
400invalid_requestMalformed request body
401unauthorizedMissing or invalid authentication
403forbiddenAuthenticated but not permitted
404not_foundResource doesn't exist
409conflictResource already exists
429rate_limitedToo many requests
500internal_errorServer error

Script-Specific Errors

Error CodeDescription
script_not_foundScript doesn't exist
script_not_approvedCannot test unapproved script
script_modifiedScript changed since approval
invalid_scriptScript has syntax errors

Rate Limiting

The Admin API enforces rate limits to prevent abuse:

EndpointLimit
/api/auth/login5 requests/minute
All other endpoints60 requests/minute

Rate Limit Headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1705323600

CORS Configuration

CORS is restricted to same-origin by default. For development, you may need to configure allowed origins:

admin:
cors:
allowed_origins:
- "http://localhost:3000"
- "http://localhost:5173"

Security Headers

All Admin API responses include security headers:

Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains

Example: Complete Workflow

1. Check Setup Status

curl http://localhost:8080/api/setup/status
# {"setup_required": true}

2. Complete Setup

curl -X POST http://localhost:8080/api/setup \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "my-secure-passphrase-here",
"confirm_password": "my-secure-passphrase-here"
}'

3. Login

curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{"username": "admin", "password": "my-secure-passphrase-here"}'

4. Get Access Token

curl http://localhost:8080/api/session \
-b cookies.txt
# {"access_token": "eyJ...", "expires_at": "...", "username": "admin"}

5. List Scripts

curl http://localhost:8080/api/scripts \
-H "Authorization: Bearer eyJ..."

6. Approve a Script

curl -X POST http://localhost:8080/api/scripts/weather.star/approve \
-H "Authorization: Bearer eyJ..."