> ## Documentation Index
> Fetch the complete documentation index at: https://figranium.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# REST API Reference

Figranium exposes a local REST API for task management, execution, and data retrieval.

**Base URL**: `http://localhost:11345` (default)

## **Health**

### `GET /api/health`

Returns the server status and storage backend connectivity. Use this endpoint for uptime monitoring, load-balancer health checks, or verifying that Figranium is ready to accept work.

**Response** (healthy):

```json theme={null}
{
  "status": "ok",
  "uptime": 3600,
  "storage": "json"
}
```

**Response** (degraded — database unreachable):

```json theme={null}
{
  "status": "degraded",
  "uptime": 3600,
  "storage": "postgres",
  "storage_error": "Database unreachable"
}
```

| Field           | Description                                   |
| :-------------- | :-------------------------------------------- |
| `status`        | `"ok"` or `"degraded"`                        |
| `uptime`        | Server uptime in seconds                      |
| `storage`       | `"json"` (file-based) or `"postgres"`         |
| `storage_error` | Present only when the database is unreachable |

Returns HTTP `200` when healthy and `503` when degraded. This endpoint does not require authentication.

## **Authentication**

If enabled, authentication must be provided via one of the following methods:

* **Header**: `x-api-key: your_key` or `key: your_key`
* **Authorization Header**: `Authorization: Bearer your_key`
* **Request Body**: `{"apiKey": "your_key"}` or `{"key": "your_key"}`

## **Tasks**

### `GET /api/tasks`

Lists all saved tasks. Each task includes its full configuration except for the version history, which is fetched separately when you open a task in the editor.

**Response**:

```json theme={null}
[
  {
    "id": "task_1",
    "name": "Scraper",
    "description": "Extracts product prices daily",
    "url": "https://example.com",
    "mode": "agent"
  }
]
```

The `description` field is optional and only present if the task has one.

### `POST /api/tasks`

Creates or updates a task. If a task with the same `id` already exists, it is overwritten. Include `?version=true` in the query string to save a version snapshot before overwriting.

**Body**:

```json theme={null}
{
  "name": "New Task",
  "description": "Optional summary of what this task does",
  "url": "https://example.com",
  "mode": "agent",
  "actions": []
}
```

The `description` field is optional. When set, it appears on the canvas trigger card and in the `GET /api/tasks/list` response so external tools and AI agents have context about the task without fetching its full definition.

### `POST /api/tasks/:id/api`

Triggers an execution of a specific task.

**Body**:

```json theme={null}
{
  "variables": {
    "query": "override value"
  },
  "webhookUrl": "https://your-server.com/callback"
}
```

| Field                | Type    | Required | Description                                              |
| :------------------- | :------ | :------- | :------------------------------------------------------- |
| `variables`          | object  | No       | Override task-level variables for this run               |
| `webhookUrl`         | string  | No       | URL to receive a POST callback with the execution result |
| `statelessExecution` | boolean | No       | Run in a clean browser context with no saved cookies     |

**Response**: Returns the execution result (JSON).

#### Completion webhook

When you include a `webhookUrl`, Figranium sends a `POST` request to that URL after the task finishes. The webhook URL is validated against the same SSRF rules as all other outbound requests (see [Security](/docs/security)). If the URL points to a private network and `ALLOW_PRIVATE_NETWORKS` is `false`, the request returns `400 INVALID_WEBHOOK_URL`.

The callback payload looks like this:

```json theme={null}
{
  "executionId": "exec_abc123",
  "taskId": "task_1",
  "status": "success",
  "durationMs": 4200,
  "result": { "data": [ ... ] }
}
```

The webhook request times out after 10 seconds. Delivery failures are logged on the server but do not affect the execution result returned to the original caller.

### `POST /api/tasks/generate-selector`

Uses the configured AI provider to generate a CSS selector from a plain-language description and the current page DOM. The endpoint runs the task up to the specified action index to capture the live HTML, then sends it to the AI provider chain (Gemini → OpenAI → Claude → Ollama) and returns the first successful result. Requires authentication.

**Body**:

```json theme={null}
{
  "task": { "url": "https://example.com", "actions": [...] },
  "actionIndex": 2,
  "prompt": "the search input field"
}
```

| Field         | Type   | Required | Description                                            |
| :------------ | :----- | :------- | :----------------------------------------------------- |
| `task`        | object | Yes      | The full task object, including `url` and `actions`    |
| `actionIndex` | number | Yes      | Run actions up to this index to capture the page state |
| `prompt`      | string | Yes      | A natural-language description of the element to find  |

**Response** (success):

```json theme={null}
{
  "selector": "input[aria-label=\"Search\"]"
}
```

**Error responses**:

| Status | Cause                                                                                   |
| :----- | :-------------------------------------------------------------------------------------- |
| `400`  | Missing `task`, `actionIndex`, or `prompt`, or no AI provider keys configured           |
| `500`  | All configured AI providers failed to generate a selector, or the DOM extraction failed |

<Note>
  Ollama requests from this endpoint go through redirect-safe fetching with SSRF validation at every hop. See [Ollama URL validation](/docs/security#ollama-url-validation) for details.
</Note>

### `POST /api/tasks/generate-script`

Uses the configured AI provider to generate a JavaScript extraction script from a plain-language description. The endpoint tries each provider in order (Gemini → OpenAI → Claude → Ollama) and returns the first successful result. Requires authentication.

**Body**:

```json theme={null}
{
  "description": "extract all article titles and links from the page"
}
```

| Field         | Type   | Required | Description                                           |
| :------------ | :----- | :------- | :---------------------------------------------------- |
| `description` | string | Yes      | A natural-language description of the data to extract |

**Response** (success):

```json theme={null}
{
  "script": "const articles = Array.from(document.querySelectorAll('article'));\nreturn articles.map(a => ({\n  title: a.querySelector('h2')?.innerText.trim(),\n  link: a.querySelector('a')?.href\n}));"
}
```

**Error responses**:

| Status | Cause                                                             |
| :----- | :---------------------------------------------------------------- |
| `400`  | Missing or empty `description`, or no AI provider keys configured |
| `502`  | All configured AI providers failed to generate a script           |

<Note>
  Ollama requests from this endpoint go through redirect-safe fetching with SSRF validation at every hop. See [Ollama URL validation](/docs/security#ollama-url-validation) for details.
</Note>

## **Executions**

### `GET /api/executions`

Lists execution history.

**Query Parameters**:

* `limit`: Number of results (default 50).
* `status`: Filter by status code.

### `GET /api/executions/:id`

Gets detailed logs and result for a specific execution.

### `DELETE /api/executions/:id`

Deletes a single execution record by ID.

**Response**:

```json theme={null}
{ "success": true }
```

### `POST /api/executions/clear`

Deletes all execution records. Use this endpoint to free disk space or reset history after large batch runs.

**Response**:

```json theme={null}
{ "success": true }
```

## **Data**

### `GET /api/data/captures`

Lists all captured files (screenshots, recordings).

### `DELETE /api/data/captures/:name`

Deletes a specific capture file.

## **Settings**

### AI provider keys

Manage API keys for each AI provider. All providers support multiple keys for automatic failover.

#### `GET /api/settings/gemini-api-key`

Returns the configured Gemini API keys.

#### `POST /api/settings/gemini-api-key`

Saves one or more Gemini API keys.

**Body**:

```json theme={null}
{
  "geminiApiKeys": ["your-gemini-key"]
}
```

#### `GET /api/settings/openai-api-key`

Returns the configured OpenAI API keys.

#### `POST /api/settings/openai-api-key`

Saves one or more OpenAI API keys.

**Body**:

```json theme={null}
{
  "openAiApiKeys": ["your-openai-key"]
}
```

#### `GET /api/settings/claude-api-key`

Returns the configured Claude (Anthropic) API keys.

#### `POST /api/settings/claude-api-key`

Saves one or more Claude API keys.

**Body**:

```json theme={null}
{
  "claudeApiKeys": ["your-claude-key"]
}
```

#### `GET /api/settings/ollama-api-key`

Returns the configured Ollama instances.

#### `POST /api/settings/ollama-api-key`

Saves one or more Ollama instance configurations. Each entry is a JSON string containing the base URL and model name.

**Body**:

```json theme={null}
{
  "ollamaApiKeys": ["{\"url\":\"http://localhost:11434\",\"model\":\"gemma4:e2b\"}"]
}
```

You can also pass a plain URL string (e.g., `"http://localhost:11434"`), in which case the default model `gemma4:e2b` is used.

### AI model settings

#### `GET /api/settings/ai-models`

Returns the currently configured default model for each AI provider. Requires authentication.

**Response**:

```json theme={null}
{
  "gemini": "gemini-3-flash-preview",
  "openai": "gpt-5-nano",
  "claude": "claude-haiku-4-6",
  "ollama": "llama3.2"
}
```

#### `POST /api/settings/ai-models`

Updates the default model for one or more AI providers. Omitted providers keep their current value. Requires authentication.

**Body**:

```json theme={null}
{
  "openai": "gpt-4o",
  "claude": "claude-sonnet-4-5"
}
```

**Response**: Returns the full updated model configuration (same shape as the `GET` response).

### Proxies

#### `GET /api/settings/proxies`

Lists configured proxies, rotation settings, and the default proxy.

**Response**:

```json theme={null}
{
  "proxies": [
    { "id": "host", "server": "host_ip", "label": "Host IP (no proxy)" },
    { "id": "proxy_a1b2c3", "server": "http://1.2.3.4:8080", "label": "US Proxy" }
  ],
  "defaultProxyId": "host",
  "includeDefaultInRotation": false,
  "rotationMode": "round-robin"
}
```

#### `POST /api/settings/proxies`

Adds a new proxy.

**Body**:

```json theme={null}
{
  "server": "http://1.2.3.4:8080",
  "username": "user",
  "password": "pass",
  "label": "US Proxy",
  "isRotatingPool": false,
  "estimatedPoolSize": 500
}
```

| Field               | Type    | Required | Description                                                           |
| :------------------ | :------ | :------- | :-------------------------------------------------------------------- |
| `server`            | string  | Yes      | Proxy address (e.g., `http://1.2.3.4:8080`, `socks5://10.0.0.5:1080`) |
| `username`          | string  | No       | Authentication username                                               |
| `password`          | string  | No       | Authentication password                                               |
| `label`             | string  | No       | Friendly display name                                                 |
| `isRotatingPool`    | boolean | No       | Mark this proxy as a rotating pool gateway                            |
| `estimatedPoolSize` | number  | No       | Approximate number of IPs behind the gateway (informational)          |

The server address is validated against the SSRF blocklist. If it points to a private or internal network address, the request returns `400` with `INVALID_URL`.

#### `POST /api/settings/proxies/import`

Bulk imports proxies. Existing proxies with matching server addresses are updated; new addresses are added.

**Body**:

```json theme={null}
{
  "proxies": [
    { "server": "http://1.2.3.4:8080", "label": "US" },
    { "server": "socks5://5.6.7.8:1080", "username": "user", "password": "pass" }
  ]
}
```

Each entry supports the same fields as `POST /api/settings/proxies`. Every entry is validated individually against the SSRF blocklist — if any entry fails, the entire import is rejected.

#### `PUT /api/settings/proxies/:id`

Updates an existing proxy by ID.

**Body**: Same fields as `POST /api/settings/proxies`. The `server` field is required and re-validated against the SSRF blocklist.

#### `DELETE /api/settings/proxies/:id`

Deletes a single proxy by ID.

#### `DELETE /api/settings/proxies`

Deletes multiple proxies by ID.

**Body**:

```json theme={null}
{
  "ids": ["proxy_a1b2c3", "proxy_d4e5f6"]
}
```

#### `POST /api/settings/proxies/rotation`

Updates rotation settings.

**Body**:

```json theme={null}
{
  "defaultProxyId": "proxy_a1b2c3",
  "includeDefaultInRotation": true,
  "rotationMode": "random"
}
```

| Field                      | Type    | Description                                                          |
| :------------------------- | :------ | :------------------------------------------------------------------- |
| `defaultProxyId`           | string  | ID of the proxy to use when rotation is off (use `null` for Host IP) |
| `includeDefaultInRotation` | boolean | Whether to include the default proxy in the rotation pool            |
| `rotationMode`             | string  | `"round-robin"` or `"random"`                                        |

## **Credentials**

### `GET /api/credentials`

Lists all saved credentials. Tokens are redacted in the response.

**Response**:

```json theme={null}
[
  {
    "id": "cred_a1b2c3d4e5f6g7h8",
    "name": "My Baserow",
    "provider": "baserow",
    "config": {
      "baseUrl": "https://baserow.io",
      "token": "••••••••"
    }
  }
]
```

### `POST /api/credentials`

Creates a new output provider credential.

**Body**:

```json theme={null}
{
  "provider": "baserow",
  "name": "My Baserow",
  "config": {
    "baseUrl": "https://baserow.io",
    "token": "your-api-token"
  }
}
```

The `baseUrl` is validated against SSRF rules. If the URL points to a private or internal network address (and `ALLOW_PRIVATE_NETWORKS` is `false`), the request returns:

```json theme={null}
{ "error": "INVALID_BASE_URL", "details": "Invalid base URL format or restricted destination" }
```

### `PUT /api/credentials/:id`

Updates an existing credential. The same URL validation applies when updating the `baseUrl`.

### `DELETE /api/credentials/:id`

Deletes a credential by ID.

**Response**:

```json theme={null}
{ "success": true }
```

### `GET /api/credentials/:id/proxy/baserow/databases`

Lists all Baserow databases accessible by the credential. This proxies the request through Figranium so your Baserow token is never exposed to the browser.

**Response**:

```json theme={null}
[
  { "id": 1, "name": "Web Scraping" },
  { "id": 2, "name": "Product Data" }
]
```

### `GET /api/credentials/:id/proxy/baserow/databases/:databaseId/tables`

Lists all tables within a specific Baserow database. The `databaseId` parameter must be a numeric value.

**Response**:

```json theme={null}
[
  { "id": 45, "name": "Products" },
  { "id": 46, "name": "Pricing History" }
]
```

**Error responses**:

| Status | Error code            | Cause                                            |
| :----- | :-------------------- | :----------------------------------------------- |
| `400`  | `INVALID_DATABASE_ID` | The `databaseId` parameter is not a valid number |

These proxy endpoints are used by the task editor's **Output** tab to let you browse and select a destination table without leaving Figranium.

### `POST /api/settings/api-key`

Generates or regenerates the system API key. If you include an `apiKey` field in the request body, that value is saved as the new key (max 512 characters). If omitted, a new key is generated automatically.

**Body** (optional):

```json theme={null}
{
  "apiKey": "your-custom-key"
}
```

**Error responses**:

| Status | Error code         | Cause                                   |
| :----- | :----------------- | :-------------------------------------- |
| `400`  | `API_KEY_TOO_LONG` | The supplied key exceeds 512 characters |

### `GET /api/settings/selector-finder`

Returns the default selector finder preference.

**Response**:

```json theme={null}
{ "selectorFinder": "highlight" }
```

Possible values: `"highlight"` (standard point-and-click picker) or `"ai"` (AI Selector Finder).

### `POST /api/settings/selector-finder`

Sets the default selector finder tool.

**Body**:

```json theme={null}
{ "selectorFinder": "ai" }
```

## **Headful**

### `GET /api/headful/status`

Returns the current state of the headful (VNC) browser session. Requires authentication.

**Response** (VNC enabled, session active):

```json theme={null}
{
  "useNovnc": true,
  "novncPort": 54311,
  "isRunning": true
}
```

**Response** (VNC disabled):

```json theme={null}
{
  "useNovnc": false
}
```

| Field       | Description                                                         |
| :---------- | :------------------------------------------------------------------ |
| `useNovnc`  | Whether VNC support is enabled on the server                        |
| `novncPort` | The port the noVNC viewer is running on (only present when enabled) |
| `isRunning` | Whether a headful browser session is currently active               |

Use this endpoint to check if a headful session is running before starting a new one, or to display connection status in a custom integration.

## **Schedules**

### `GET /api/schedules`

Lists all tasks with an associated schedule.

**Response**:

```json theme={null}
{
  "schedules": [
    {
      "taskId": "c1f7a08b",
      "taskName": "Daily Price Check",
      "schedule": {
        "enabled": true,
        "frequency": "daily",
        "hour": 9,
        "minute": 0,
        "nextRun": 1690099200000
      }
    }
  ]
}
```

### `POST /api/schedules/:taskId`

Creates or updates a schedule for a task.

**Body**:

```json theme={null}
{
  "enabled": true,
  "frequency": "daily",
  "hour": 9,
  "minute": 0
}
```

For advanced scheduling, use a cron expression:

```json theme={null}
{
  "enabled": true,
  "cron": "0 9 * * 1-5"
}
```

**Response**:

```json theme={null}
{
  "schedule": { ... },
  "description": "Every day at 9:00 AM",
  "nextRun": 1690099200000
}
```

### `DELETE /api/schedules/:taskId`

Disables the schedule for a task.

**Response**:

```json theme={null}
{ "success": true }
```

### `GET /api/schedules/status/all`

Returns runtime status of the scheduler and all active jobs.

**Response**:

```json theme={null}
{
  "running": true,
  "scheduledCount": 2,
  "tasks": [
    {
      "taskId": "c1f7a08b",
      "cron": "0 9 * * *",
      "nextRun": "2024-07-23T09:00:00.000Z"
    }
  ]
}
```
