Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/arrozet/caret/llms.txt

Use this file to discover all available pages before exploring further.

Caret’s AI agents can propose targeted edits to your document — a rewrite of a paragraph, a completion of an unfinished sentence, or a structural reorganisation. These proposals are called suggestions. Rather than writing changes directly into the database, the AI emits a suggestion record in proposed state. The frontend renders it as inline ghost text inside the Tiptap editor; the user then accepts or rejects it. Only if the user accepts does the change flow through a Tiptap transaction, keeping the Y.js collaborative document state consistent for all connected collaborators. Three endpoints manage the full suggestion lifecycle: POST /api/v1/ai/suggestions to create a suggestion, GET /api/v1/ai/suggestions to list suggestions for a conversation, and PATCH /api/v1/ai/suggestions/{suggestion_id}/status to advance a suggestion through its lifecycle. All routes are proxied through the API Gateway at https://api.caret.page/api/v1/ai/... and require a valid Supabase JWT.

Suggestion lifecycle

Every suggestion starts in the proposed state the moment it is created by the AI service. From there it can move to one of three terminal states:
proposed
  ├── applied     (user accepted — change was committed to the document)
  ├── dismissed   (user rejected — change was discarded)
  └── superseded  (a newer suggestion replaced this one before the user acted)
StatusMeaning
proposedThe AI has generated a suggestion and it is awaiting the user’s decision. Ghost text is visible in the editor.
appliedThe user accepted the suggestion. The frontend has already applied the change via a Tiptap transaction; this PATCH call records that outcome.
dismissedThe user rejected the suggestion. The ghost text is removed from the editor and the suggestion is archived.
supersededA newer suggestion for the same document position was generated before the user acted on this one. The old suggestion is automatically retired.
The AI service never writes directly to document content tables. All accepted suggestions reach the document exclusively through Tiptap editor transactions, which Y.js then propagates to every collaborator in real time. This guarantees that the CRDT document state is always the source of truth — the suggestion record in the database is metadata only.

How suggestions surface in the editor

  1. The user sends a message to the AI via POST /api/v1/ai/conversations/{conversation_id}/stream with agent_type: "general".
  2. The general agent analyses the document and, when it decides to propose an edit, emits a document_change SSE event containing a DocumentChangePayload with proposed_text, original_text, and operation.
  3. After the stream ends, the AI service persists the suggestion to the ai_suggestions table in proposed state and populates suggestion_id on the final event.
  4. The frontend displays the diff as inline ghost text using Tiptap’s decoration system.
  5. The user clicks Accept or Reject in the editor UI.
    • Accept: The frontend applies the change as a Tiptap transaction → Y.js syncs it to all collaborators → frontend calls PATCH …/status with { "status": "applied" }.
    • Reject: The frontend discards the ghost text → calls PATCH …/status with { "status": "dismissed" }.

POST /api/v1/ai/suggestions

Create a new suggestion record in proposed state. In normal usage the AI service creates suggestion records automatically as part of the stream flow (triggered by a document_change SSE event). This endpoint is available for cases where the frontend needs to persist a suggestion independently.
curl -X POST https://api.caret.page/api/v1/ai/suggestions \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation_id": "f7e6d5c4-b3a2-1098-fedc-ba9876543210",
    "document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "suggested_text": "The report reveals a clear upward trend in Q3 adoption metrics.",
    "original_text": "The report shows mixed results.",
    "position_start": 142,
    "position_end": 174
  }'

Request body

conversation_id
string
required
UUID of the parent conversation.
document_id
string
required
UUID of the document this suggestion targets. The authenticated user must have access to this document.
suggested_text
string
required
The full replacement text proposed by the AI. Minimum 1 character.
message_id
string
UUID of the assistant message that generated this suggestion. Optional.
original_text
string
The document text that was present at the target position when the suggestion was generated. null for insertion-only suggestions.
position_start
integer
Character offset of the start of the targeted range. null for whole-document suggestions.
position_end
integer
Character offset of the end of the targeted range. null for whole-document suggestions.

Response — 201 Created

Returns the full suggestion object in proposed state. The response schema is identical to the PATCH response documented below.

Error responses

StatusCondition
404 Not FoundThe document_id does not exist.
403 ForbiddenThe authenticated user does not have access to the document.

GET /api/v1/ai/suggestions

List all suggestions for a conversation, optionally filtered by status.
curl "https://api.caret.page/api/v1/ai/suggestions?conversation_id=f7e6d5c4-b3a2-1098-fedc-ba9876543210&status=proposed" \
  -H "Authorization: Bearer <token>"

Query parameters

conversation_id
string
required
UUID of the conversation whose suggestions to list.
status
string
Optional lifecycle status filter. When omitted all suggestions are returned. One of proposed, applied, dismissed, superseded.

Response — 200 OK

Returns a JSON array of suggestion objects. Each object has the same fields as the PATCH response documented below.
[
  {
    "id": "cc112233-4455-6677-8899-aabbccddeeff",
    "conversation_id": "f7e6d5c4-b3a2-1098-fedc-ba9876543210",
    "document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "message_id": "bbccddee-2233-4455-6677-889900112233",
    "status": "proposed",
    "original_text": "The report shows mixed results.",
    "suggested_text": "The report reveals a clear upward trend in Q3 adoption metrics.",
    "position_start": 142,
    "position_end": 174,
    "created_at": "2024-11-15T10:35:00Z",
    "updated_at": "2024-11-15T10:35:00Z"
  }
]

PATCH /api/v1/ai/suggestions//status

Transition a suggestion to its next lifecycle state. The authenticated user must have access to the document the suggestion belongs to; the service validates document access before applying the status change.
curl -X PATCH \
  "https://api.caret.page/api/v1/ai/suggestions/cc112233-4455-6677-8899-aabbccddeeff/status" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{ "status": "applied" }'

Path parameters

suggestion_id
string
required
UUID of the suggestion to update.

Request body

status
string
required
The new lifecycle status. Must be one of:
  • applied — the user accepted the suggestion and it has been committed to the document.
  • dismissed — the user rejected the suggestion.
  • superseded — a newer suggestion has replaced this one (typically set programmatically by the frontend when a second suggestion arrives for the same position).

Response — 200 OK

Returns the full updated suggestion object.
id
string
required
UUID of the suggestion.
conversation_id
string
required
UUID of the parent conversation.
document_id
string
required
UUID of the document this suggestion targets.
message_id
string
UUID of the assistant message that generated this suggestion, or null.
status
string
required
Updated lifecycle status: applied, dismissed, or superseded.
original_text
string
The document text that was present at the position when the suggestion was generated, or null for insertion-only suggestions.
suggested_text
string
required
The full replacement text proposed by the AI.
position_start
integer
Character offset of the start of the targeted range in the document, or null for whole-document suggestions.
position_end
integer
Character offset of the end of the targeted range in the document, or null for whole-document suggestions.
created_at
string
required
ISO 8601 creation timestamp.
updated_at
string
required
ISO 8601 last-updated timestamp (reflects the status change).
Example response
{
  "id": "cc112233-4455-6677-8899-aabbccddeeff",
  "conversation_id": "f7e6d5c4-b3a2-1098-fedc-ba9876543210",
  "document_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "message_id": "bbccddee-2233-4455-6677-889900112233",
  "status": "applied",
  "original_text": "The report shows mixed results.",
  "suggested_text": "The report reveals a clear upward trend in Q3 adoption metrics.",
  "position_start": 142,
  "position_end": 174,
  "created_at": "2024-11-15T10:35:00Z",
  "updated_at": "2024-11-15T10:36:12Z"
}

Error responses

StatusCondition
404 Not FoundThe suggestion UUID does not exist.
403 ForbiddenThe authenticated user does not have access to the document the suggestion belongs to.

TypeScript — updating suggestion status

async function updateSuggestionStatus(
  suggestionId: string,
  status: "applied" | "dismissed" | "superseded",
  token: string,
): Promise<void> {
  const response = await fetch(
    `https://api.caret.page/api/v1/ai/suggestions/${suggestionId}/status`,
    {
      method: "PATCH",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ status }),
    },
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Failed to update suggestion: ${error.detail}`);
  }
}

// Called from the editor's "Accept" button handler
async function onAcceptSuggestion(
  editor: Editor,
  suggestion: AiSuggestion,
  token: string,
): Promise<void> {
  // 1. Apply the change through a Tiptap transaction first
  editor
    .chain()
    .focus()
    .setTextSelection({ from: suggestion.position_start, to: suggestion.position_end })
    .insertContent(suggestion.suggested_text)
    .run();

  // 2. Record the outcome in the AI service
  await updateSuggestionStatus(suggestion.id, "applied", token);
}
Always apply the Tiptap transaction before calling this endpoint. If the PATCH fails after the transaction has been committed, the document is still in the correct state — the status update is metadata and can be retried safely.

Build docs developers (and LLMs) love