# REST API

> Gin routes under /api/v1: auth, flows, tasks, subtasks, files, resources, containers, toolcalls, assistants, logs, knowledge, providers, settings, users, tokens; Swagger at /api/v1/swagger.

- Repository: vxcontrol/pentagi
- GitHub: https://github.com/vxcontrol/pentagi
- Human docs: https://grok-wiki.com/public/docs/vxcontrol-pentagi-eca0cf4de5f5
- Complete Markdown: https://grok-wiki.com/public/docs/vxcontrol-pentagi-eca0cf4de5f5/llms-full.txt

## Source Files

- `backend/pkg/server/router.go`
- `backend/pkg/server/docs/swagger.yaml`
- `backend/pkg/server/services/flows.go`
- `backend/pkg/server/services/api_tokens.go`
- `backend/pkg/server/services/knowledge.go`
- `backend/pkg/server/auth/auth_middleware.go`
- `README.md`

---

---
title: "REST API"
description: "Gin routes under /api/v1: auth, flows, tasks, subtasks, files, resources, containers, toolcalls, assistants, logs, knowledge, providers, settings, users, tokens; Swagger at /api/v1/swagger."
---

PentAGI mounts its HTTP surface on Gin at **`/api/v1`** (`baseURL` in `backend/pkg/server/router.go`). The same engine serves REST handlers, GraphQL at `ANY /api/v1/graphql`, Swagger UI at `GET /api/v1/swagger/*any`, CORS, cookie sessions named `auth`, and optional static SPA or reverse-proxy frontend. OpenAPI metadata is generated into `backend/pkg/server/docs/swagger.yaml` (swag tags on service methods; `@BasePath /api/v1`, `@securityDefinitions.apikey BearerAuth`).

## Auth and access tiers

Middleware lives in `backend/pkg/server/auth/auth_middleware.go`. Three patterns apply on `/api/v1`:

| Group | Middleware | Routes |
| --- | --- | --- |
| Public | `TryAuth` (optional cookie or Bearer) | `GET /info`, `GET /swagger/*any`, `GET /graphql/playground`, `/auth/*` |
| Private | `AuthTokenRequired` (Bearer **or** session cookie) | Flows, tasks, knowledge, providers, settings, logs, files, resources, assistants, prompts, usage, GraphQL, roles, users, tokens |
| Password | `AuthUserRequired` + `localUserRequired` | `PUT /user/password` |

**Session login.** `POST /auth/login` accepts `models.Login` (`mail`, `password`), verifies bcrypt for local users (not OAuth-only / role_id `100`), loads privileges from `privileges` by `role_id`, and sets a signed cookie session (`HttpOnly`, `SameSite=Lax`, path `/api/v1`, max age **4 hours**). OAuth: `GET /auth/authorize?provider=google|github&return_uri=...`, then GET/POST `/auth/login-callback`. Logout: `GET /auth/logout`, `POST /auth/logout-callback`.

**Bearer API tokens.** Header: `Authorization: Bearer <jwt>`. Tokens are signed with `CookieSigningSalt`. If salt is empty or the literal `"salt"`, Bearer validation is skipped and token **creation** returns `Token.CreationDisabled`. Active tokens set privileges from the token cache; status must be `active`; user must not be blocked; user hash must match the installation.

**Privilege checks.** Handlers inspect `c.GetStringSlice("prm")` (for example `flows.view`, `flows.create`, `flows.edit`, `flows.delete`, `flows.admin`, `knowledge.*`, `providers.view`, `settings.view`, `settings.tokens.admin`). Missing privilege → `403` with code `NotPermitted`. Non-admin scopes typically filter by `user_id`.

**Guest info.** `GET /info` with `TryAuth` returns guest vs authenticated shape, privileges, OAuth provider names configured on the server, and develop-mode flags.

## Response envelope

All REST handlers use `backend/pkg/server/response`:

**Success**

```json
{ "status": "success", "data": { } }
```

**Error**

```json
{
  "status": "error",
  "code": "AuthRequired",
  "msg": "auth required",
  "error": "optional detail when develop mode is on"
}
```

Common codes: `AuthRequired` (403), `NotPermitted` (403), `PrivilegesRequired` (403), `Token.CreationDisabled` (400), `Token.NotFound` (404), resource-specific `*.NotFound` / `*.InvalidRequest`.

## Swagger

| Item | Value |
| --- | --- |
| UI | `GET /api/v1/swagger/index.html` (gin-swagger under `/swagger/*any`) |
| Spec | Generated `swagger.yaml` / `swagger.json` in `backend/pkg/server/docs/` |
| Security | `BearerAuth` header scheme on protected operations |
| Title | PentAGI Swagger API v1.0 |

Swagger and GraphQL playground sit on the public group (no hard auth required to open the UI; API calls still need credentials).

## List query protocol (`rdb.TableQuery`)

Paginated list endpoints bind query params from `backend/pkg/server/rdb/table.go`:

| Field | Query key | Rules |
| --- | --- | --- |
| Page | `page` | Required, min 1, default 1 |
| Size | `pageSize` | −1…1000 (−1 = unlimited), default 5 |
| Type | `type` | `init` \| `sort` \| `filter` \| `page` \| `size` |
| Sort | `sort[]` | `{prop, order}` with `ascending` / `descending` |
| Filters | `filters[]` | `{field, value, operator}`; operators `< <= >= > = != like not like in` |
| Group | `group` | Optional distinct-value grouping |

Typical list success body: `{ "items-or-entity-key": [...], "total": N }`. Flows group response: `{ "grouped": [...], "total": N }`.

## Auth routes

| Method | Path | Body / notes |
| --- | --- | --- |
| `POST` | `/auth/login` | `{ "mail", "password" }` |
| `GET` | `/auth/logout` | Clears session |
| `GET` | `/auth/authorize` | Query: `provider`, optional `return_uri` |
| `GET` | `/auth/login-callback` | Query: `code`, `state` (cookie state must match) |
| `POST` | `/auth/login-callback` | `models.AuthCallback` |
| `POST` | `/auth/logout-callback` | OAuth logout callback |
| `GET` | `/info` | Session/token introspection |
| `PUT` | `/user/password` | Local user password change; session required |

## Flows, tasks, subtasks

Flow lifecycle statuses: `created`, `running`, `waiting`, `finished`, `failed`.

| Method | Path | Privilege pattern | Notes |
| --- | --- | --- | --- |
| `GET` | `/flows/` | `flows.view` or `flows.admin` | TableQuery; admin sees all |
| `GET` | `/flows/:flowID` | view/admin | Single flow |
| `GET` | `/flows/:flowID/graph` | view/admin | Flow + tasks + subtasks |
| `POST` | `/flows/` | `flows.create` | Creates flow via `FlowController` |
| `PUT` | `/flows/:flowID` | `flows.edit` or admin | Actions: `stop`, `finish`, `input`, `rename` |
| `DELETE` | `/flows/:flowID` | `flows.delete` or admin | Finishes then deletes |
| `GET` | `/flows/:flowID/tasks/` | view scope | Task list |
| `GET` | `/flows/:flowID/tasks/:taskID` | | Task detail |
| `GET` | `/flows/:flowID/tasks/:taskID/graph` | | Task graph |
| `GET` | `/flows/:flowID/subtasks/` | | All subtasks for flow |
| `GET` | `/flows/:flowID/tasks/:taskID/subtasks/` | | Subtasks for task |
| `GET` | `/flows/:flowID/tasks/:taskID/subtasks/:subtaskID` | | One subtask |

:::endpoint POST /api/v1/flows/ Create flow
Create a pentest (or general) flow and start agent execution.

**Auth:** Bearer or session · **Privilege:** `flows.create`

**Body (`models.CreateFlow`)**

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | string | yes | First-task user objective |
| `provider` | string | yes | Provider name (BYOK/env or UI profile) |
| `functions` | object | no | Optional tool function allowlist |
| `resource_ids` | uint64[] | no | User resources to attach |

**Responses:** `201` → `models.Flow` · `403` not permitted · `500` provider/controller failure
:::

:::endpoint PUT /api/v1/flows/{flowID} Patch flow
Drive lifecycle actions on an existing flow.

**Body (`models.PatchFlow`)**

| Field | Type | Required when | Values / notes |
| --- | --- | --- | --- |
| `action` | string | always | `stop` \| `finish` \| `input` \| `rename` |
| `input` | string | `action=input` | Non-empty user message (`PutInput`) |
| `provider` | string | optional on input | Override provider for this input |
| `name` | string | `action=rename` | New title |
| `resource_ids` | uint64[] | optional on input | Attach resources |

Maps to controller: `Stop`, `Finish`, `PutInput`, `Rename`.
:::

## Assistants

Nested under a flow:

| Method | Path | Body |
| --- | --- | --- |
| `POST` | `/flows/:flowID/assistants/` | `CreateAssistant`: `input`, `provider`, `use_agents`, optional `functions`, `resource_ids` |
| `GET` | `/flows/:flowID/assistants/` | List |
| `GET` | `/flows/:flowID/assistants/:assistantID` | Detail |
| `PUT` | `/flows/:flowID/assistants/:assistantID` | `PatchAssistant`: `action` = `stop` \| `input`; optional `use_agents`, `resource_ids` |
| `DELETE` | `/flows/:flowID/assistants/:assistantID` | Delete |

## Files, resources, containers, toolcalls

**Flow files** (`/flows/:flowID/files/`) — host/container workspace under data dir + Docker:

| Method | Path | Role |
| --- | --- | --- |
| `GET` | `/` | List flow files |
| `GET` | `/container` | List container-side files |
| `POST` | `/` | Upload |
| `DELETE` | `/` | Delete |
| `GET` | `/download` | Download |
| `POST` | `/pull` | Pull from container |
| `POST` | `/resources` | Add existing resources into flow |
| `POST` | `/to-resources` | Promote flow file to user resource |

**User resources** (`/resources/`):

| Method | Path | Role |
| --- | --- | --- |
| `GET` | `/` | List |
| `POST` | `/` | Upload |
| `POST` | `/mkdir` | Create directory |
| `PUT` | `/move` | Move/rename |
| `POST` | `/copy` | Copy |
| `DELETE` | `/` | Delete |
| `GET` | `/download` | Download |

**Containers / toolcalls** — read-only inventory:

- `GET /containers/`, `GET /flows/:flowID/containers/`, `GET /flows/:flowID/containers/:containerID`
- `GET /toolcalls/`, `GET /flows/:flowID/toolcalls/`, `GET /flows/:flowID/toolcalls/:toolcallID`

## Logs and screenshots

Global list + per-flow list (TableQuery where applicable):

| Domain | Global | Per flow |
| --- | --- | --- |
| Agent logs | `GET /agentlogs/` | `GET /flows/:flowID/agentlogs/` |
| Assistant logs | `GET /assistantlogs/` | `GET /flows/:flowID/assistantlogs/` |
| Message logs | `GET /msglogs/` | `GET /flows/:flowID/msglogs/` |
| Terminal logs | `GET /termlogs/` | `GET /flows/:flowID/termlogs/` |
| Search logs | `GET /searchlogs/` | `GET /flows/:flowID/searchlogs/` |
| Vector-store logs | `GET /vecstorelogs/` | `GET /flows/:flowID/vecstorelogs/` |
| Screenshots | `GET /screenshots/` | `GET /flows/:flowID/screenshots/`, `.../:screenshotID`, `.../:screenshotID/file` |

## Knowledge (pgvector)

REST mirrors GraphQL knowledge ops via `KnowledgeService` + shared `knowledge.KnowledgeStore`. Collection name `langchain`; documents with `doc_type=memory` are excluded from list. Embedder unavailable → create/search return **503** (`ErrKnowledgeStoreUnavail`).

| Method | Path | Privilege intent |
| --- | --- | --- |
| `GET` | `/knowledge/` | view/admin; TableQuery + `with_content=true\|1` |
| `GET` | `/knowledge/:id` | UUID |
| `POST` | `/knowledge/` | create + embed |
| `POST` | `/knowledge/search` | semantic search |
| `PUT` | `/knowledge/:id` | update + re-embed |
| `DELETE` | `/knowledge/:id` | delete |

Filter fields on list: `id`, `doc_type`, `question`, `description`, `guide_type`, `answer_type`, `code_lang`, `manual`, `user_id`, `flow_id`, `task_id`, `subtask_id`, `data`.

**Create body (`CreateKnowledgeDocRequest`)**

| Field | Constraints |
| --- | --- |
| `doc_type` | `answer` \| `guide` \| `code` |
| `content` | 1…65536 chars |
| `question` | 1…2048 chars |
| `description` | optional, max 1000 |
| `guide_type` / `answer_type` / `code_lang` | optional typed metadata |

**Search body (`KnowledgeSearchRequest`):** `query` (required), `limit` 1…100, optional `doc_types`, `guide_types`, `answer_types`, `code_langs`, `flow_id`, `manual`.

## Providers, settings, prompts, usage

| Method | Path | Privilege | Returns |
| --- | --- | --- | --- |
| `GET` | `/providers/` | `providers.view` | Provider names, types, default model, model price/info for current user |
| `GET` | `/settings/` | `settings.view` | `debug`, `ask_user`, binary `version`, `docker_inside`, develop mode, `assistant_use_agents` |
| `GET` | `/prompts/` | | All prompt templates |
| `GET` | `/prompts/:promptType` | | One template |
| `PUT` | `/prompts/:promptType` | | Patch custom template |
| `POST` | `/prompts/:promptType/default` | | Reset to default |
| `DELETE` | `/prompts/:promptType` | | Delete custom override |
| `GET` | `/usage/` | | System usage |
| `GET` | `/usage/:period` | | Period usage |
| `GET` | `/flows/:flowID/usage` | | Flow token/usage stats |
| `POST` | `/anonymize/text` | | PII-style text anonymization (shared pattern replacer) |

Provider list is BYOK-oriented: only configured/available providers for the authenticated user appear; no hardcoded cloud vendor is required.

## Users, roles, tokens

| Method | Path | Notes |
| --- | --- | --- |
| `GET` | `/user/` | Current user |
| `GET` | `/users/` | List (admin-scoped where applicable) |
| `GET` | `/users/:hash` | By user hash |
| `POST` | `/users/` | Create |
| `PUT` | `/users/:hash` | Patch |
| `DELETE` | `/users/:hash` | Delete |
| `GET` | `/roles/` | Roles |
| `GET` | `/roles/:roleID` | Role detail |
| `POST` | `/tokens/` | Create API token (JWT secret returned once) |
| `GET` | `/tokens/` | List (own, or all with `settings.tokens.admin`) |
| `GET` | `/tokens/:tokenID` | Metadata |
| `PUT` | `/tokens/:tokenID` | Update `name` / `status` |
| `DELETE` | `/tokens/:tokenID` | Soft-delete / revoke |

:::endpoint POST /api/v1/tokens/ Create API token
Issues a JWT usable as `Authorization: Bearer` on REST and GraphQL.

**Body (`CreateAPITokenRequest`)**

| Field | Type | Constraints |
| --- | --- | --- |
| `name` | string | optional, max 100, unique per user |
| `ttl` | uint64 | required, **60…94608000** seconds (1 min…3 years) |

**Responses:** `201` → `APITokenWithSecret` (`token` JWT + metadata) · `400` default salt / validation · `403` unauthorized

Statuses: `active`, `revoked`, `expired` (expired may be computed when `created_at + ttl` is past).
:::

## GraphQL co-location

| Method | Path | Auth |
| --- | --- | --- |
| `ANY` | `/graphql` | `AuthTokenRequired` |
| `GET` | `/graphql/playground` | Public (`TryAuth`) |

Realtime subscriptions and some mutations exist only on GraphQL; REST covers CRUD-style and list/report surfaces above.

## Request examples

<RequestExample>
```bash title="Login (session cookie)"
curl -sk -c cookies.txt -X POST 'https://localhost:8443/api/v1/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"mail":"admin@pentagi.com","password":"YOUR_PASSWORD"}'
```
```bash title="Create flow with Bearer token"
curl -sk -X POST 'https://localhost:8443/api/v1/flows/' \
  -H 'Authorization: Bearer YOUR_JWT' \
  -H 'Content-Type: application/json' \
  -d '{"input":"Scan https://target.example for OWASP top 10","provider":"openai"}'
```
```bash title="List flows (TableQuery)"
curl -sk 'https://localhost:8443/api/v1/flows/?page=1&pageSize=20&type=init' \
  -H 'Authorization: Bearer YOUR_JWT'
```
```bash title="Stop a running flow"
curl -sk -X PUT 'https://localhost:8443/api/v1/flows/42' \
  -H 'Authorization: Bearer YOUR_JWT' \
  -H 'Content-Type: application/json' \
  -d '{"action":"stop"}'
```
</RequestExample>

## Failure modes

| Symptom | Likely cause |
| --- | --- |
| `403` `AuthRequired` | Missing/expired session or invalid Bearer |
| `403` `NotPermitted` | Role lacks resource privilege |
| `400` `Token.CreationDisabled` | `CookieSigningSalt` still default `"salt"` or empty |
| Bearer ignored / skip | Same default salt disables token validation path |
| Knowledge `503` | Embedder not configured or pgvector store init failed at boot |
| Flow create `500` | Unknown `provider` name for user, or `FlowController` error |
| OAuth authorize `403` | Provider not configured (`OAuthGoogle*` / `OAuthGithub*` env) |
| Password change `403` | Non-local user or missing session-only middleware |

## Architecture (request path)

```text
Client (UI / curl / automation)
        │
        ▼
  Gin /api/v1  ── CORS · sessions · no-cache · recovery · logger
        │
   ┌────┴─────────────────────┐
   │ TryAuth (public)         │ AuthTokenRequired (private)
   │  auth, info, swagger     │  REST resources + GraphQL
   └────────────┬─────────────┘
                ▼
     services/*  →  GORM / FlowController / ProviderController
                │         KnowledgeStore (pgvector)
                ▼
           response.Success | response.Error
```

## Next

<CardGroup>
  <Card title="Authentication and API tokens" href="/auth-and-api-tokens">
    Session login, OAuth, default admin, Bearer lifecycle and privilege scopes.
  </Card>
  <Card title="GraphQL API" href="/graphql-api">
    Query, Mutation, and Subscription surface including real-time log streams.
  </Card>
  <Card title="Flows, tasks, and subtasks" href="/flows-tasks-subtasks">
    StatusType lifecycle, putUserInput / stop / finish boundaries for REST actions.
  </Card>
  <Card title="Memory and knowledge" href="/memory-and-knowledge">
    pgvector documents, embeddings, flow files and resources under /work.
  </Card>
  <Card title="Configure LLM providers" href="/configure-llm-providers">
    Wire BYOK providers used by POST /flows and GET /providers.
  </Card>
</CardGroup>
