> For the complete documentation index, see [llms.txt](https://docs.gofigr.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.gofigr.io/api-reference/endpoints.md).

# REST Endpoints

Complete documentation of all HTTP REST endpoints in the GoFigr server API.

**Base URL:** `https://api.gofigr.io`\
**Supported API Versions:** `v1`, `v1.1`, `v1.2`, `v1.3`, `v1.4`, `v1.4.1`

All endpoints are versioned: `/api/{version}/resource/`

**Authentication:**

* JWT Bearer token: `Authorization: Bearer {access_token}`
* API Key: `Authorization: Token {api_key}`

***

## Table of Contents

1. [Authentication](#authentication)
2. [Common Resource Behavior](#common-resource-behavior)
3. [Bootstrap](#bootstrap)
4. [API Info](#api-info)
5. [Site Settings](#site-settings)
6. [Organizations](#organizations)
7. [Workspaces](#workspaces)
8. [Analyses](#analyses)
9. [Figures](#figures)
10. [Figure Revisions](#figure-revisions)
11. [Assets](#assets)
12. [Asset Revisions](#asset-revisions)
13. [External Data](#external-data)
14. [Stories](#stories)
15. [Comments](#comments)
16. [Reactions](#reactions)
17. [Deep Insight (AI)](#deep-insight-ai)
18. [Search](#search)
19. [Managed Compute](#managed-compute)
20. [Tasks](#tasks)
21. [Users](#users)
22. [Plans](#plans)
23. [API Keys](#api-keys)
24. [SSH Keys](#ssh-keys)
25. [Git Repository](#git-repository)
26. [Data Upload](#data-upload)
27. [Invitations](#invitations)
28. [Billing](#billing)
29. [Auth0](#auth0)
30. [Short IDs](#short-ids)
31. [AI Usage](#ai-usage)
32. [Metadata Proxy](#metadata-proxy)
33. [API Version History](#api-version-history)

***

## Authentication

### Obtain JWT Token Pair

```
POST /api/token/
```

Authenticates user and returns access/refresh token pair.

**Request Body:**

```json
{
  "username": "string",
  "password": "string",
  "remember_me": true  // optional, extends token lifetime
}
```

**Response:** `200 OK`

```json
{
  "access": "string",
  "refresh": "string"
}
```

***

### Refresh Access Token

```
POST /api/token/refresh/
```

**Request Body:**

```json
{
  "refresh": "string"
}
```

**Response:** `200 OK`

```json
{
  "access": "string"
}
```

***

### Password Reset

```
POST /api/password_reset/
```

**Request Body:**

```json
{
  "email": "string"
}
```

***

## Common Resource Behavior

The core resources — **Organizations, Workspaces, Analyses, Figures, Figure Revisions, Assets, Asset Revisions, Stories, and External Data** — are all backed by the same viewset machinery and therefore share a common set of CRUD routes, sub-actions, and query parameters. Rather than repeat them under every resource, they are described once here. Resource-specific fields and extra actions are documented in each section below.

### Standard CRUD routes

For a resource mounted at `/{resource}/`:

```
GET    /api/{version}/{resource}/             # List (where supported)
POST   /api/{version}/{resource}/             # Create        → 201, 409 on duplicate client_id
GET    /api/{version}/{resource}/{api_id}/    # Retrieve
PUT    /api/{version}/{resource}/{api_id}/    # Full update
PATCH  /api/{version}/{resource}/{api_id}/    # Partial update
DELETE /api/{version}/{resource}/{api_id}/    # Delete         → 204
```

Objects are addressed by their `api_id` (a UUID). Permissions are enforced per action (`VIEW`, `CREATE`, `UPDATE`, `DELETE`, `MANAGE`, `SHARE`). Moving an object to a new parent (e.g. changing a figure's `analysis`) on update requires move permission and is rejected with `400` if the type is not movable.

### Common sub-actions

Available on every core resource (`{api_id}` detail routes):

```
GET  /api/{version}/{resource}/{api_id}/size/         # → { "size_bytes": int }
GET  /api/{version}/{resource}/{api_id}/children/     # → [ { "entity_type", "api_id" }, ... ] (recursive)
GET  /api/{version}/{resource}/{api_id}/thumbnail/    # → { "format", "thumbnail" } (base64)
GET  /api/{version}/{resource}/{api_id}/log/          # Activity log for the entity
GET  /api/{version}/{resource}/{api_id}/log/{log_id}/ # Single enhanced log item
GET  /api/{version}/{resource}/{api_id}/share/user/   # List users the object is shared with
POST /api/{version}/{resource}/{api_id}/share/user/   # { "username", "sharing_enabled" }
GET  /api/{version}/{resource}/{api_id}/share/link/   # Link-sharing status
POST /api/{version}/{resource}/{api_id}/share/link/   # { "enabled": bool }
```

* **`thumbnail/{size}/`** — an optional size segment scales the thumbnail. Pass `?dl=1` (or an `Accept: image/*` header) to receive raw PNG bytes instead of JSON.
* **`log/`** — supports `deep=true` (full activity items, paginated), `exclude_deleted=true`, `deduplicate=true`, `offset`, and `limit`.

### Common query parameters

| Parameter     | Applies to             | Effect                                                                                                |
| ------------- | ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `silent=true` | create, update, delete | Mark the resulting activity-log entry as silent (still recorded, but hidden from most activity views) |
| `dl=1`        | `thumbnail/`           | Return binary PNG instead of JSON                                                                     |

### Common response fields

All core resources include these read-only fields in their serialized form:

* `api_id`, `entity_type`, `size_bytes`
* `created_by`, `updated_by` (usernames), `created_on`, `updated_on`, `last_activity_on`
* `created_on_behalf`, `created_on_behalf_name`, `created_on_behalf_email` *(v1.3+)*

***

## Bootstrap

### Get Bootstrap Data

```
GET /api/{version}/bootstrap/
```

Returns all data needed for initial frontend load in a single request, eliminating multiple round trips.

**Response:** `200 OK`

```json
{
  "user": {...},
  "workspaces": [...],
  "settings": {...},
  "info": {
    "api_version": "string"
  }
}
```

***

## API Info

### Get API Info

```
GET /api/{version}/info/
```

Public endpoint (no authentication required) describing the running server.

**Response:** `200 OK`

```json
{
  "environment": "string",
  "server_version": "string",
  "started": "datetime",
  "api_versions": ["v1", "v1.1", "v1.2", "v1.3", "v1.4", "v1.4.1"],
  "your_ip": "string",
  "ai_enabled": true
}
```

When Auth0 is configured, the response also includes `auth0_domain`, `auth0_spa_client_id`, `auth0_cli_client_id`, and `auth0_audience`.

***

## Site Settings

### Get Site Settings

```
GET /api/{version}/settings/
```

Returns the singleton site-settings object (feature flags, AI configuration, limits). Includes a read-only `ai_supported_models` list of available AI models.

***

## Organizations

Organizations group workspaces and manage shared subscriptions, billing, and membership.

Supports the [standard CRUD routes and common sub-actions](#common-resource-behavior).

**Response fields:** `api_id`, `name`, `description`, `logo` (base64 PNG, nullable), `allow_link_sharing`, `allow_per_workspace_storage_settings`, `workspaces` (read-only, shallow), plus the common timestamp fields.

### Create Organization

```
POST /api/{version}/organization/
```

**Request Body:**

```json
{
  "name": "string",
  "description": "string"
}
```

### Members

```
GET  /api/{version}/organization/{api_id}/members/
POST /api/{version}/organization/{api_id}/members/add/
POST /api/{version}/organization/{api_id}/members/remove/
POST /api/{version}/organization/{api_id}/members/change/
```

Manage organization membership. `members/` returns `[{ "username", "membership_type" }]`. Requires `MANAGE`.

### Subscription

```
GET   /api/{version}/organization/{api_id}/subscription/
POST  /api/{version}/organization/{api_id}/subscription/
PATCH /api/{version}/organization/{api_id}/subscription/
```

Get or change the organization's plan, or toggle `compute_overages_enabled`. Requires `MANAGE`.

### Flexible Storage

```
GET  /api/{version}/organization/{api_id}/storage/
POST /api/{version}/organization/{api_id}/storage/
POST /api/{version}/organization/{api_id}/storage/test/
```

Configure a custom (BYO) storage backend. `storage/test/` validates vendor/credentials without saving.

### Invitations

```
GET /api/{version}/organization/{api_id}/invitations/
```

Lists valid pending invitations for the organization. Requires `MANAGE`. See [Invitations](#invitations) for create/accept.

### Compute Usage

```
GET /api/{version}/organization/{api_id}/compute_usage/?period=YYYY-MM
```

Billable managed-compute cost summary for the given month (defaults to month-to-date). Requires `MANAGE`.

***

## Workspaces

Supports the [standard CRUD routes and common sub-actions](#common-resource-behavior).

**Response fields:** `api_id`, `name`, `description`, `workspace_type`, `organization`, `size_bytes` (read-only), `compute_enabled` (read-only), and — in the full serializer — read-only nested `analyses`, `assets`, and `stories`. The list endpoint returns a lightweight representation in v1.3+.

### List Workspaces

```
GET /api/{version}/workspace/
```

Lists all workspaces the user has access to (directly or via an organization).

### Get Workspace

```
GET /api/{version}/workspace/{api_id}/
```

### Create Workspace

```
POST /api/{version}/workspace/
```

**Request Body:**

```json
{
  "name": "string",
  "description": "string",
  "workspace_type": "secondary"
}
```

Primary workspaces are assigned at user creation and cannot be created through the API.

### Get Workspace Overview

```
GET /api/{version}/workspace/{api_id}/overview/
```

Returns aggregated counts for the workspace.

**Response:** `200 OK`

```json
{
  "analysis_count": 10,
  "figure_count": 45,
  "asset_count": 5,
  "story_count": 3,
  "active_this_week": 2
}
```

### Get Workspace Dashboard

```
GET /api/{version}/workspace/{api_id}/dashboard/
```

Returns all data needed for the home view in a single request.

**Query Parameters:**

* `activity_limit` (integer): Number of activity items (default: 10, max: 50)
* `exclude_deleted` (boolean): Exclude deleted items from activity log
* `deduplicate` (boolean): Collapse activity to the newest entry per target

**Response:** `200 OK`

```json
{
  "workspace": {...},
  "overview": {...},
  "activity_log": {
    "items": [...],
    "has_more": true,
    "total_count": 100,
    "fetched_count": 10
  },
  "stories": [...]
}
```

### Get Recent Activity

```
GET /api/{version}/workspace/{api_id}/recent/?limit=20
```

Returns recently active assets, analyses, and figures (`limit` default 20, max 1000).

### Apply Promotion

```
POST /api/{version}/workspace/{api_id}/promotion/
```

**Request Body:**

```json
{ "promotion_code": "string" }
```

Applies a promotion code and returns the resulting plan. Requires `MANAGE`.

### Members

```
GET  /api/{version}/workspace/{api_id}/members/
POST /api/{version}/workspace/{api_id}/members/add/
POST /api/{version}/workspace/{api_id}/members/remove/
POST /api/{version}/workspace/{api_id}/members/change/
```

### Subscription & Storage

```
GET   /api/{version}/workspace/{api_id}/subscription/
POST  /api/{version}/workspace/{api_id}/subscription/
GET   /api/{version}/workspace/{api_id}/storage/
POST  /api/{version}/workspace/{api_id}/storage/
POST  /api/{version}/workspace/{api_id}/storage/test/
```

Manage the workspace plan and custom storage. Workspaces governed by an organization-level subscription return `409` with `managed_by_organization: true`.

### Compute Instances

```
GET  /api/{version}/workspace/{api_id}/compute_instances/
POST /api/{version}/workspace/{api_id}/compute_instances/
GET  /api/{version}/workspace/{api_id}/compute_instances/launch_options/
GET  /api/{version}/workspace/{api_id}/compute_allowance/
GET  /api/{version}/workspace/{api_id}/compute_usage/
```

List and launch managed-compute instances in the workspace. See [Managed Compute](#managed-compute) for the instance lifecycle and `POST` body. *(v1.4.1+)*

***

## Analyses

An analysis is a container for figures within a workspace.

Supports the [standard CRUD routes and common sub-actions](#common-resource-behavior).

**Response fields:** `api_id`, `name`, `description`, `workspace` (api\_id), `figures` (read-only), `assets` (read-only), `thumbnail` (read-only), `size_bytes`, `is_imported`, `import_source`, `import_source_asset`, plus common fields.

### Create Analysis

```
POST /api/{version}/analysis/
```

**Request Body:**

```json
{
  "name": "string",
  "description": "string",
  "workspace": "uuid"
}
```

***

## Figures

A figure is a named slot within an analysis; its content lives in [figure revisions](#figure-revisions).

Supports the [standard CRUD routes and common sub-actions](#common-resource-behavior).

**Response fields:** `api_id`, `name`, `description`, `analysis` (api\_id), `revisions` (read-only, shallow), `thumbnail` (read-only), `size_bytes`, `auto_assign_pending`, `is_imported`, `import_source`, `import_source_asset`, plus common fields.

### Create Figure

```
POST /api/{version}/figure/
```

**Request Body:**

```json
{
  "name": "string",
  "description": "string",
  "analysis": "uuid"
}
```

***

## Figure Revisions

A figure revision is an immutable snapshot of a figure's image, code, and data.

Supports the [standard CRUD routes and common sub-actions](#common-resource-behavior).

**Response fields:** `api_id`, `short_id`, `figure` (api\_id), `metadata` (JSON), `revision_index` (read-only), `data` (read-only; shallow in v1.2+), `assets` (read-only links), `thumbnail`, `figure_metadata` (read-only context object), `image_hash`, `size_bytes`, `is_processing`, `is_clean_room`, `description`, `description_timestamp`, `description_author`, `is_imported`, `import_source`, `import_source_asset`, plus common fields. `client_id` (UUID) is **write-only** and sets the revision's primary key (v1.4.1+).

### Create Figure Revision

```
POST /api/{version}/revision/
```

**Request Body:**

```json
{
  "figure": "uuid",
  "metadata": {...},
  "short_id": "string",        // optional; prefix must be reserved by the user
  "is_clean_room": false,
  "data": [                    // ExternalData objects (base64 data)
    { "name": "string", "type": "image", "metadata": {...}, "data": "base64" }
  ]
}
```

Data is processed asynchronously — poll the [`status`](#get-revision-status) action.

### Get Revision Status

```
GET /api/{version}/revision/{api_id}/status/
```

**Response:**

```json
{ "is_processing": false }
```

### Comment Count

```
GET /api/{version}/revision/{api_id}/comment_count/
```

Returns the number of top-level comments: `{ "count": int }`.

### Generate Description (AI)

```
POST /api/{version}/revision/{api_id}/generate_description/
```

Generates an AI description for the revision and stores it (author set to the `ai` system user). Requires AI to be enabled. Returns the updated revision.

### Derive Revision

```
POST /api/{version}/revision/{api_id}/derive/
```

Creates a new revision by cloning the source revision's data objects (same storage paths), returning the new revision immediately with its `api_id` so a watermark can be generated before uploading image data via [`append_data`](#append-data).

**Request Body (all optional):**

```json
{
  "figure": "uuid",       // target figure; defaults to the source's figure
  "metadata": {...}
}
```

### Append Data

```
POST /api/{version}/revision/{api_id}/append_data/
```

Appends data objects to an existing revision without deleting existing data (used after `derive` to upload watermarked images, code, and manifests).

**Request Body:**

```json
{ "data": [ /* ExternalData objects with base64 data */ ] }
```

### Auto-Assign Revision (AI)

```
POST /api/{version}/revision/auto-assign/
```

Creates a revision under a temporary figure and dispatches AI tasks to assign it to the most appropriate figure in the analysis.

**Request Body:**

```json
{
  "analysis": "uuid",
  "metadata": {...},
  "data": [ /* ExternalData objects */ ]
}
```

***

## Assets

Assets are reusable data objects (datasets, tables, files) that can be linked to figures.

Supports the [standard CRUD routes and common sub-actions](#common-resource-behavior).

**Response fields:** `api_id`, `name`, `description`, `workspace` (api\_id), `analysis` (api\_id, optional), `revisions` (read-only, shallow), `thumbnail`, `size_bytes`, plus common fields.

### Create Asset

```
POST /api/{version}/asset/
```

**Request Body:**

```json
{
  "name": "string",
  "description": "string",
  "workspace": "uuid",
  "analysis": "uuid"   // optional
}
```

### Find by Name

```
POST /api/{version}/asset/find_by_name/
```

Finds assets by exact name, optionally scoped to an analysis.

**Request Body:**

```json
{
  "name": "string",
  "analysis": "uuid"   // optional
}
```

**Response:** `200 OK` — JSON array of matching assets the user can view.

***

## Asset Revisions

An asset revision is an immutable snapshot of an asset's data.

Supports the [standard CRUD routes and common sub-actions](#common-resource-behavior).

**Response fields:** `api_id`, `asset` (api\_id), `metadata` (JSON), `data` (read-only, shallow), `revision_index`, `thumbnail`, `data_hash`, `figure_revisions` (read-only links), `size_bytes`, `is_processing`, plus common fields. `client_id` (UUID) is **write-only**.

### Create Asset Revision

```
POST /api/{version}/asset_revision/
```

**Request Body:**

```json
{
  "asset": "uuid",
  "metadata": {...},
  "data": [ /* ExternalData objects with base64 data */ ]
}
```

### Get Revision Status

```
GET /api/{version}/asset_revision/{api_id}/status/
```

**Response:** `{ "is_processing": false }`

### Find by Hash

```
POST /api/{version}/asset_revision/find_by_hash/
```

Finds asset revisions by content hash. Used for deduplication during sync.

**Request Body:**

```json
{
  "hash_type": "blake3",
  "digest": "string",
  "analysis": "uuid"  // Optional
}
```

* `hash_type` *(required)*: Hash algorithm. Currently only `blake3` is supported.
* `digest` *(required)*: The hex-encoded content hash.
* `analysis` *(optional)*: API ID of an analysis. When provided, only revisions whose parent asset belongs to that analysis are returned. When omitted, returns **all** matching revisions workspace-wide, including both scoped and unscoped assets. Note: omitting `analysis` does **not** filter to unscoped-only assets.

**Response:** `200 OK` — JSON array of matching asset revision objects, or an empty array if none found.

**Python Client:** `AssetRevision.find_by_hash(digest, hash_type="blake3", analysis=None)` **R Client:** `find_asset_revision_by_hash(gf, digest, hash_type="blake3")`

### Unlink Figure

```
POST /api/{version}/asset_revision/{api_id}/unlink_figure/
```

Unlinks a figure revision from an asset revision.

**Request Body:**

```json
{
  "figure_revision": "uuid",
  "anchor": "string",  // Optional
  "delete_figure_revision": false  // Optional
}
```

***

## External Data

Read-only access to the individual data objects (images, code, tables, manifests) attached to figure and asset revisions. Data objects are created and updated through their parent revision, not directly — `POST`, `PUT`, `PATCH`, and `DELETE` on this resource return `405 Method Not Allowed`.

```
GET /api/{version}/data/{api_id}/
```

**Response fields:** `api_id`, `name`, `type`, `metadata` (JSON), `data` (base64; null when shallow), `size_bytes`, `hash`, `is_clean_room`.

### Storage Info

```
GET /api/{version}/data/{api_id}/storage/
```

Returns the storage backend `vendor`, `hash`, and (for admins or third-party storage) the `path` of the underlying object. Requires `MANAGE` on the workspace.

***

## Stories

Stories are AI-generated presentations from figure collections.

### List Stories

```
GET /api/{version}/story/
```

***

### Get Story

```
GET /api/{version}/story/{api_id}/
```

**Response:**

```json
{
  "api_id": "uuid",
  "name": "string",
  "description": "string",
  "workspace": "uuid",
  "revisions": [...],
  "slides": [...],
  "created_on": "datetime",
  "updated_on": "datetime"
}
```

***

### Create Story

```
POST /api/{version}/story/
```

**Request Body:**

```json
{
  "name": "string",
  "description": "string",
  "workspace": "uuid",
  "revisions": ["uuid", "uuid", ...],  // Figure revision IDs
  "generate": false                     // Optional; if true, dispatch AI generation
}
```

When `generate` is `true`, the story is created and an asynchronous generation task is dispatched; the response story carries a `generation_status` of `generating` (poll via the [Tasks](#tasks) API).

***

### Update Story

```
PUT /api/{version}/story/{api_id}/
PATCH /api/{version}/story/{api_id}/
```

**Request Body:**

```json
{
  "name": "string",
  "description": "string",
  "revisions": ["uuid", ...],  // Replaces all revisions
  "slides": [  // Replaces all slides
    {
      "slide_id": "string",
      "slide_type": "title|introduction|figure|goals|data|conclusion|custom",
      "position": 0,
      "content": "string",
      "revision_id": "uuid",  // For figure slides
      "slide_data": {...}
    }
  ]
}
```

For figure slides, server-owned AI fields (`methods`, `data`, `availability`) are preserved if omitted from the client payload.

***

### Delete Story

```
DELETE /api/{version}/story/{api_id}/
```

***

## Comments

Comments support Markdown, @mentions, and threading.

### List Comments

```
GET /api/{version}/comment/?target_type={type}&target_id={uuid}
```

**Query Parameters (required):**

* `target_type`: `asset`, `asset_revision`, `figure`, or `figure_revision`
* `target_id`: API ID of the target object

**Response:** `200 OK`

```json
[
  {
    "id": "uuid",
    "user": {...},
    "content": "string",
    "parent_comment_id": "uuid|null",
    "is_edited": false,
    "created_on": "datetime",
    "updated_on": "datetime"
  }
]
```

***

### Create Comment

```
POST /api/{version}/comment/
```

**Request Body:**

```json
{
  "target_type": "figure_revision",
  "target_id": "uuid",
  "content": "Great analysis! @username what do you think?",
  "parent_comment_id": "uuid"  // Optional, for replies
}
```

Mentions using `@username` trigger email notifications.

***

### Update Comment

```
PUT /api/{version}/comment/{id}/
```

Only the comment author can edit. Sets `is_edited: true`.

**Request Body:**

```json
{
  "content": "Updated comment text"
}
```

***

### Delete Comment

```
DELETE /api/{version}/comment/{id}/
```

Only the comment author can delete.

***

### Create AI Response

```
POST /api/{version}/comment/{parent_id}/create_ai_response/
```

Creates an AI-authored reply to the specified comment. The AI generates the response asynchronously.

**Response:** `201 Created` - The empty AI comment (content populated async)

***

## Reactions

Emoji reactions on comments.

### List Reactions

```
GET /api/{version}/reaction/?comment_id={uuid}
```

***

### Create Reaction

```
POST /api/{version}/reaction/
```

**Request Body:**

```json
{
  "comment_id": "uuid",
  "emoji": "👍"
}
```

***

### Delete Reaction

```
DELETE /api/{version}/reaction/{id}/
```

***

## Deep Insight (AI)

AI-powered analysis of figures using Amazon Bedrock. All Deep Insight endpoints require AI to be enabled on the site (`503` otherwise) and are subject to plan-based rate limiting and token quotas (`429` when exhausted).

### Query Deep Insight

```
POST /api/{version}/deep_insight/
```

**Request Body:**

```json
{
  "figure_revision": "uuid",
  "prompt": "Explain this figure",
  "stream": false,
  "model": "us.amazon.nova-pro-v2:0"  // Optional
}
```

**Response (non-streaming):**

```json
{
  "response": "string"
}
```

**Response (streaming):** Server-Sent Events

***

### Compare Revisions

```
POST /api/{version}/deep_insight/compare/
```

**Request Body:**

```json
{
  "left_revision": "uuid",
  "right_revision": "uuid",
  "stream": true
}
```

***

### Extract Figure Code

```
POST /api/{version}/deep_insight/figure_code/
```

Extracts the code that generated a figure using AI.

**Request Body:**

```json
{
  "figure_revision": "uuid"
}
```

**Response:**

```json
{
  "code": "string"
}
```

***

### Edit Figure Code (AI)

```
POST /api/{version}/deep_insight/code_edit/
```

Agentic AI editing of a clean-room figure's code. Accepts a conversation and returns the assistant's reply along with revised, validated code.

**Request Body:**

```json
{
  "figure_revision": "uuid",
  "messages": [ {...} ],
  "current_code": "string",
  "figure_image": "data:image/png;base64,...",  // optional
  "available_packages": ["numpy", "pandas"],     // optional
  "model": "string"                              // optional
}
```

**Response:**

```json
{
  "role": "assistant",
  "content": "string",
  "code": "string",
  "valid": true,
  "error": null,
  "tool_calls": [...]
}
```

***

### Get Datasets

```
POST /api/{version}/deep_insight/datasets/
```

Returns data inputs/outputs for a figure revision.

**Response:**

```json
{
  "external_data": [...],
  "ai_inputs": [...],
  "ai_outputs": [...]
}
```

***

### Text-Only Query

```
POST /api/{version}/deep_insight/text_only/
```

AI query without a figure (text-only context).

**Request Body:**

```json
{
  "text": "string",
  "prompt": "string"
}
```

***

### Check Availability

```
POST /api/{version}/deep_insight/check/
```

Checks if AI can process a figure (permissions, rate limits).

**Response:**

```json
{
  "available": true,
  "messages": []
}
```

***

### Story Generation Endpoints

Optimized AI endpoints used to build and refine [stories](#stories).

```
POST /api/{version}/deep_insight/story/figure/
POST /api/{version}/deep_insight/story/overviews/
POST /api/{version}/deep_insight/story/refine/
```

Synchronous helpers that generate per-figure content, story overviews, and refinements.

#### Generate Single Slide

```
POST /api/{version}/deep_insight/story/slide/generate/
```

**Request Body:**

```json
{
  "story_id": "uuid",
  "slide_type": "introduction|goals|data|conclusion|figure|...",
  "revision_id": "uuid",   // required for figure slides
  "model": "string"        // optional
}
```

**Response (figure slide):** `{ "content", "methods", "inputs", "outputs" }` **Response (overview slide):** `{ "content", "figure_descriptions", "figure_methods" }`

#### Adjust Slide Detail

```
POST /api/{version}/deep_insight/story/slide/detail/
```

Rewrites a slide with more or less detail.

**Request Body:**

```json
{
  "story_id": "uuid",
  "slide_id": "string",
  "direction": "more",      // "more" | "less"
  "current_content": "string",
  "model": "string"          // optional
}
```

**Response:** `{ "content": "string" }`

#### Generate Full Story (Async)

```
POST /api/{version}/deep_insight/story/generate/
```

Submits asynchronous generation of an entire story. Cancels any in-flight generation and sets the story's `generation_status` to `generating`.

**Request Body:** `{ "story_id": "uuid" }` **Response:** `{ "task_id": "uuid" }` — poll via the [Tasks](#tasks) API.

#### Refine Full Story (Async)

```
POST /api/{version}/deep_insight/story/refine_async/
```

Submits asynchronous refinement of a story (`generation_status` becomes `refining`).

**Request Body:** `{ "story_id": "uuid" }` **Response:** `{ "task_id": "uuid" }`

***

## Search

### Search

```
POST /api/{version}/search/
```

Unified search across workspaces, analyses, figures, figure revisions, assets, and asset revisions. Results are filtered by per-object permissions.

**Request Body:**

```json
{
  "search_type": "keyword",   // "keyword" | "text" | "semantic" | "image"
  "query": "string",          // for keyword/text/semantic
  "keywords": ["string"],      // alternative to query (joined)
  "image": "base64",           // required for image search
  "k": 10,                     // optional max results (default 10)
  "workspace": "uuid"          // optional; restrict to one workspace
}
```

* **`keyword` / `text`** — full-text search across all indexed entity types, with highlight snippets.
* **`semantic`** — cross-modal (Nova/Bedrock) k-NN search over figure revisions; requires `query`.
* **`image`** — reverse-image search; requires a base64-encoded `image`.

**Response:** `200 OK` — array sorted by descending score:

```json
[
  {
    "score": 0.87,
    "object": {...},        // shallow-serialized matching entity
    "highlight": {...}       // per-field match snippets (keyword only)
  }
]
```

Returns `400` on a missing or unsupported `search_type`, or a missing query.

***

## Managed Compute

Cloud notebook/IDE instances managed by GoFigr. *(Available in v1.4.1+.)* Instances are **listed and launched** through their workspace, then **operated** through the `compute/instance/` routes.

### List / Launch Instances

```
GET  /api/{version}/workspace/{api_id}/compute_instances/
POST /api/{version}/workspace/{api_id}/compute_instances/
GET  /api/{version}/workspace/{api_id}/compute_instances/launch_options/
GET  /api/{version}/workspace/{api_id}/compute_allowance/
GET  /api/{version}/workspace/{api_id}/compute_usage/?period=YYYY-MM
```

**Launch Request Body:**

```json
{
  "name": "string",          // required
  "tier": "string",           // optional tier id
  "data_volume_gb": 50         // optional
}
```

`launch_options/` returns the available tiers and limits; `compute_allowance/` and `compute_usage/` report remaining allowance and billable usage.

### Instance Lifecycle

```
GET   /api/{version}/compute/instance/{api_id}/
PATCH /api/{version}/compute/instance/{api_id}/
POST  /api/{version}/compute/instance/{api_id}/start/
POST  /api/{version}/compute/instance/{api_id}/stop/
POST  /api/{version}/compute/instance/{api_id}/terminate/
POST  /api/{version}/compute/instance/{api_id}/change-tier/
GET   /api/{version}/compute/instance/{api_id}/connect/
GET   /api/{version}/compute/instance/{api_id}/heartbeat/
GET   /api/{version}/compute/instance/{api_id}/events/
GET   /api/{version}/compute/instance/{api_id}/usage/?period=YYYY-MM
```

* **`PATCH`** updates `name`, `idle_shutdown_enabled`, and `idle_shutdown_seconds`.
* **`start` / `stop` / `terminate`** drive the lifecycle (`202 Accepted`). `start` may return `402 allowance_exhausted` or `409 tier_unavailable`.
* **`change-tier`** — body `{ "tier": "string" }`; the instance must be stopped (`409` otherwise).
* **`connect`** — body-less `GET` (so a browser can navigate directly). Query `app` (default `jupyter`). Returns `{ "redirect_url", "expires_in_seconds" }` with a short-lived signed URL. Owner only.
* **`heartbeat`** (GET) — latest supervisor status: `{ "heartbeat_at", "heartbeat": { "jupyter_up", "active_kernel_count", "cpu_pct", "mem_pct", "disk_pct", ... } }`.

A `404` is returned for both nonexistent and inaccessible instances.

**Instance fields (selected):** `api_id`, `name`, `tier`, `workspace`, `owner`, `user_facing_status`, `lifecycle`, `health`, `status_detail`, `last_activity_at`, `running_since`, `idle_shutdown_enabled`, `idle_shutdown_seconds`, `idle_seconds`, `can_operate`, `can_connect`, `can_terminate`, `created_at`, `updated_at`.

### Tier Catalog

```
GET /api/{version}/compute/tiers/
```

Lists the public compute-tier catalog (label, vCPU, memory, description).

***

## Tasks

Background task tracking for user-initiated asynchronous work (story generation, imports, etc.). *(Available in v1.3+.)* Read-only; scoped to the authenticated user's own tasks.

### List Tasks

```
GET /api/{version}/tasks/
```

**Query Parameters:** `status__in`, `created_at__gte`, `created_at__lte`, `completed_at__gte`, `completed_at__lte`.

### Get Task

```
GET /api/{version}/tasks/{task_id}/
```

**Response fields:** `task_id`, `task_name`, `task_type`, `status`, `progress`, `status_message`, `task_detail`, `result`, `error`, `created_on`, `started_on`, `completed_on`. Cancelled tasks report status `REVOKED`.

### Task Logs

```
GET /api/{version}/tasks/{task_id}/logs/
```

**Response:** `{ "task_id": "uuid", "logs": [...] }`

### Cancel Task

```
POST /api/{version}/tasks/{task_id}/cancel/
```

Revokes the task in Celery and marks it cancelled. Returns `400` if the task already succeeded, failed, or was revoked.

***

## Users

Users are addressed by **username** (not UUID).

**Response fields:** `username`, `email`, `first_name`, `last_name`, `date_joined`, `is_active`, `is_staff`, `avatar`, `email_confirmed`, `using_auth0`, `user_profile`. Sensitive fields (`email`, `user_profile`, `email_confirmed`, `is_active`, `is_staff`) are stripped when viewing a user other than yourself.

### List / Search Users

```
GET /api/{version}/user/?q={query}
```

### Get User

```
GET /api/{version}/user/{username}/
```

### Create User

```
POST /api/{version}/user/
```

**Request Body:**

```json
{
  "username": "string",
  "email": "string",
  "password": "string",
  "first_name": "string",
  "last_name": "string"
}
```

**Response:** `201 Created` — the created user plus an `auth` object containing JWT tokens.

### Update User

```
PUT   /api/{version}/user/{username}/
PATCH /api/{version}/user/{username}/
```

Self only. Accepts `email`, `first_name`, `last_name`, `password`, `avatar`, and a nested `user_profile` payload.

### Contributions

```
GET /api/{version}/user/{username}/contributions/
```

Returns the authenticated user's activity counts per date over the last 365 days.

### Verify Email

```
POST /api/{version}/user/{username}/verify_email/
```

With a `token` in the body, confirms the user's email. Without a token, generates and emails a new verification token.

***

## Plans

### List Plans

```
GET /api/{version}/plan/
```

Lists available subscription plans (read-only).

**Response fields:** `api_id`, `name`, `description`, `monthly_cost`, `annual_cost`, `max_storage_bytes`, `max_users`, `allow_flexible_storage`, `monthly_token_quota`, `deep_insight_rate`, `compute_enabled`, `compute_max_running_instances`, `idle_shutdown_required`, `max_idle_seconds`, `default_idle_seconds`, `effective_max_data_volume_gb`.

***

## API Keys

Programmatic access tokens (used by the Python and R clients). For security, API keys can only be **created or deleted** with username/password (JWT) authentication — not while authenticated with an API key.

### List API Keys

```
GET /api/{version}/api_key/
```

**Response:** array of `{ "api_id", "name", "token": null, "expiry", "last_used", "created", "workspace" }`. The secret `token` is **never** returned on list/retrieve.

### Get API Key

```
GET /api/{version}/api_key/{api_id}/
```

### Create API Key

```
POST /api/{version}/api_key/
```

**Request Body:**

```json
{
  "name": "string",        // required, unique per user
  "workspace": "uuid",      // optional; scopes the key to one workspace
  "expiry": "datetime"      // optional; must be in the future
}
```

**Response:** `201 Created`

```json
{
  "api_id": "uuid",
  "name": "string",
  "token": "string",       // the secret — returned ONLY on create
  "expiry": "datetime",
  "last_used": null,
  "created": "datetime",
  "workspace": "uuid",
  "user": "string"
}
```

### Delete API Key

```
DELETE /api/{version}/api_key/{api_id}/
```

***

## SSH Keys

Manage SSH keys for Git repository imports.

### List SSH Keys

```
GET /api/{version}/ssh_key/
```

**Response:**

```json
[
  {
    "api_id": "uuid",
    "name": "string",
    "fingerprint": "SHA256:...",
    "is_default": true,
    "created_on": "datetime"
  }
]
```

***

### Add SSH Key

```
POST /api/{version}/ssh_key/
```

**Request Body:**

```json
{
  "name": "My Git Key",
  "private_key": "-----BEGIN OPENSSH PRIVATE KEY-----...",
  "is_default": false
}
```

Note: Private key is stored encrypted. Never returned in API responses.

***

### Update SSH Key

```
PATCH /api/{version}/ssh_key/{api_id}/
```

**Request Body:**

```json
{
  "name": "New Name",
  "is_default": true
}
```

***

### Delete SSH Key

```
DELETE /api/{version}/ssh_key/{api_id}/
```

***

## Git Repository

### Check Repository Access

```
POST /api/{version}/git/check/
```

Validates Git repository URL and authentication.

**Request Body:**

```json
{
  "url": "https://github.com/user/repo.git",
  "ssh_key_id": "uuid"  // Optional, for SSH URLs
}
```

***

## Data Upload

### Upload User Data

```
POST /api/{version}/user_data_upload/
```

Upload and process files (Git repos, PowerPoint, Word docs).

***

## Invitations

Invite users to a workspace or organization. The same routes exist for both targets — substitute `workspace` or `organization` for `{target}`.

### Create Invitation

```
POST /api/{version}/invitations/{target}/
```

Requires `MANAGE` on the target. An email is sent to the recipient.

**Request Body:**

```json
{
  "workspace": "uuid",         // or "organization": "uuid"
  "email": "string",
  "membership_type": "string",
  "expiry": "datetime"          // optional; capped to a maximum TTL
}
```

**Response:** `201 Created` — the serialized invitation. The `token` field is only populated in debug builds.

### Get Invitation

```
GET /api/{version}/invitations/{target}/{token}
```

Looks up an invitation by its token (any valid invite) or by api\_id (requires `MANAGE`). Returns `404` if expired or not found.

### Accept Invitation

```
POST /api/{version}/invitations/{target}/{token}/accept
```

Adds the requesting user to the target with the invitation's membership type and consumes the invite.

**Response:** `200 OK` — `{ "message": "Invitation accepted successfully" }`. Returns `400` if the collaborator limit has been reached.

### Delete Invitation

```
DELETE /api/{version}/invitations/{target}/{api_id}
```

Revokes a pending invitation (the path segment must be the invitation's api\_id). Requires `MANAGE`.

***

## Billing

Stripe-backed subscription management. Every request targets exactly one of `workspace_api_id` or `organization_api_id`; the caller needs `MANAGE` on the target. Organization-managed workspaces are rejected with `409` and `managed_by_organization: true`.

### Create Checkout Session

```
POST /api/{version}/billing/checkout/
```

Starts a Stripe Checkout session for a new paid subscription.

**Request Body:**

```json
{
  "plan_api_id": "uuid",
  "frequency": "monthly",          // or "annual"
  "workspace_api_id": "uuid"        // or "organization_api_id"
}
```

**Response:** `200 OK` — `{ "checkout_url", "session_id" }`. Returns `409` if an active subscription already exists (use change-plan).

### Open Billing Portal

```
POST /api/{version}/billing/portal/
```

Returns a Stripe Customer Portal URL.

**Request Body:**

```json
{ "workspace_api_id": "uuid" }     // or "organization_api_id"
```

**Response:** `200 OK` — `{ "portal_url" }`. Returns `409` if there is no Stripe customer yet.

### Change Plan

```
POST /api/{version}/billing/change-plan/
```

Switches an existing paid subscription to a different plan or billing frequency (with proration).

**Request Body:**

```json
{
  "plan_api_id": "uuid",
  "frequency": "annual",
  "workspace_api_id": "uuid"        // or "organization_api_id"
}
```

**Response:** `200 OK` — `{ "subscription_id", "plan_api_id", "frequency", "message" }`.

***

## Auth0

Endpoints for accounts managed via Auth0. All return `503` when Auth0 is not configured.

### Resend Verification Email

```
POST /api/{version}/auth0/resend-verification/
```

Resends the email-verification message for the bearer-token user. **Response:** `{ "detail": "Verification email sent" }`.

### Change Password

```
POST /api/{version}/auth0/change-password/
```

Returns a password-change ticket URL. Body (optional): `{ "return_url": "string" }`. **Response:** `{ "url": "string" }`.

### MFA Status

```
GET /api/{version}/auth0/mfa-status/
```

**Response:** `{ "enrolled": true, "methods": ["..."] }`.

### MFA Enroll

```
POST /api/{version}/auth0/mfa-enroll/
```

Returns a Guardian enrollment ticket URL. **Response:** `{ "url": "string" }`.

***

## Short IDs

Short IDs are compact, shareable identifiers for figure revisions. Clients reserve a unique prefix, then append a locally generated base62 index.

### Reserve Prefix

```
POST /api/{version}/short_id_prefix/reserve
```

Reserves a unique short-ID prefix for the authenticated user.

**Response:** `201 Created` — `{ "prefix": "string" }`.

### Resolve Short ID

```
GET /api/{version}/resolve/{short_id}
```

Resolves a short ID to a figure revision. No authentication is required, but the revision's `VIEW` permission is still enforced.

**Response:** `200 OK` — `{ "api_id": "uuid", "short_id": "string" }`. Returns `404` for both nonexistent and inaccessible revisions (to avoid leaking which short IDs are valid).

***

## AI Usage

### Get AI Usage

```
GET /api/{version}/ai/usage/
```

Returns AI usage statistics and quota information.

***

## Metadata Proxy

Short-lived secret tokens that let a running notebook/Jupyter host push figure metadata to GoFigr regardless of origin (open CORS, secured by a ≤60-second token). Used by real-time capture integrations.

### Create Token

```
POST /api/{version}/metadata/
```

Creates a token (requires authentication). Optional `expiry` (capped to a maximum TTL). **Response:** `201 Created` — the serialized token including its secret `token` value.

### Read Token

```
GET /api/{version}/metadata/{token}
```

Returns the token object. Only the user who created it may read it.

### Push Metadata

```
POST /api/{version}/metadata/{token}
```

Stores metadata against the token.

**Request Body:** `{ "metadata": {...} }`

***

## API Version History

| Version    | Key Changes                                                                                             |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| **v1.4.1** | Managed compute API, client-provided revision IDs (`client_id`), shallow create responses for revisions |
| **v1.4**   | AI-generated titles and descriptions (`ai_title`, `ai_description` fields)                              |
| **v1.3**   | Tasks API, Comments, Reactions, `created_on_behalf` fields, optimized workspace list                    |
| **v1.2**   | Lazy-loaded revision data (data returned separately, not inline)                                        |
| **v1.1**   | Nested objects in responses (analyses/figures return full objects, not just IDs)                        |
| **v1**     | Original API                                                                                            |

### v1.4.1 (Latest)

* **Managed Compute:** Cloud notebook/IDE instances (`/compute/instance/`, workspace launch routes)
* **Client-provided IDs:** Revisions accept a write-only `client_id` to set their primary key
* **Shallow create responses:** Figure/asset revision creation returns a lightweight representation

### v1.4

* Added `ai_title` and `ai_description` fields to figures and revisions
* AI-generated descriptions for imported content

### v1.3

* **Tasks API:** Background task tracking (`/tasks/` endpoint)
* **Comments & Reactions:** Full collaboration features
* **Attribution:** `created_on_behalf`, `created_on_behalf_name`, `created_on_behalf_email` fields
* **Performance:** ShallowWorkspaceSerializer for list operations

### v1.2

* **Revision data:** Data objects returned via separate fetch, not inline with revision
* Improves performance for large revisions

### v1.1

* **Nested objects:** Workspace responses include full analysis objects (not just IDs)
* Analysis responses include full figure objects

### v1

* Original API with core CRUD operations

***

## Notes

1. **API Versioning:** Use `v1.4.1` for new integrations.
2. **Data Processing:** Revisions are processed asynchronously. Check the `status` endpoint.
3. **Shallow Representations:** List endpoints and v1.2+ revision data return lightweight objects; fetch full data separately.
4. **Silent Operations:** Use `?silent=true` to mark an operation's activity entry as silent — it is still recorded but hidden from most activity views.
5. **Base64 Encoding:** Binary data (images, files) is base64-encoded.
6. **Rate Limiting:** AI endpoints are rate-limited based on plan.

***

**Last Updated:** June 2026\
**API Versions Supported:** v1, v1.1, v1.2, v1.3, v1.4, v1.4.1
