Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 145 additions & 2 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,149 @@ GET /api/v1/websites/{domain}/urls

## Platform API

The Platform service exposes a public API at `/api/v1/*` for programmatic access to your data. Authenticate using an `x-api-key` header with a key from Settings > API Keys.
The Platform service exposes a public API at `/api/v1/*` for programmatic access to your data. Authenticate using an API key from **Settings > API Keys**.

Full API documentation: `https://yourdomain.com/api/v1/openapi.json`
### OpenAI-compatible chat completions

The platform provides an interface fully compatible with the [OpenAI Chat Completions API](https://platform.openai.com/docs/api-reference/chat). Any client or SDK that supports OpenAI (Python, Node, curl, LiteLLM, etc.) can connect by pointing `base_url` to your Tale instance.

#### Quick start

<CodeGroup>

```python Python
from openai import OpenAI

client = OpenAI(
base_url="https://your-tale-instance.com/api/v1",
api_key="tale_...", # from Settings > API Keys
default_headers={"X-Organization-Slug": "default"},
)

response = client.chat.completions.create(
model="chat-agent", # agent slug from your Agents page
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
```

```typescript Node.js
import OpenAI from "openai";

const client = new OpenAI({
baseURL: "https://your-tale-instance.com/api/v1",
apiKey: "tale_...",
defaultHeaders: { "X-Organization-Slug": "default" },
});

const response = await client.chat.completions.create({
model: "chat-agent",
messages: [{ role: "user", content: "Hello!" }],
});
console.log(response.choices[0].message.content);
```

```bash curl
curl https://your-tale-instance.com/api/v1/chat/completions \
-H "Authorization: Bearer tale_..." \
-H "X-Organization-Slug: default" \
-H "Content-Type: application/json" \
-d '{"model":"chat-agent","messages":[{"role":"user","content":"Hello!"}]}'
```

</CodeGroup>

#### Authentication

All requests require a Bearer token in the `Authorization` header:

```text
Authorization: Bearer tale_...
```

Create API keys in **Settings > API Keys** in the platform UI.

#### Headers

| Header | Required | Description |
| --- | --- | --- |
| `Authorization` | Yes | `Bearer <api-key>` |
| `X-Organization-Slug` | No | Organization slug. Auto-resolved if user belongs to one org. |
| `X-Thread-Id` | No | Reuse a conversation thread across requests. |

#### Endpoints

##### POST /api/v1/chat/completions

Send a chat message and receive a response. Supports streaming and tool calling.

**Request body:**

| Field | Type | Description |
| --- | --- | --- |
| `model` | string | **Required.** Agent slug (e.g., `chat-agent`). |
| `messages` | array | **Required.** Conversation messages with `role` and `content`. |
| `stream` | boolean | Enable SSE streaming. Default: `false`. |
| `temperature` | number | Sampling temperature (0–2). |
| `max_tokens` | number | Maximum tokens to generate. |
| `top_p` | number | Nucleus sampling parameter. |
| `frequency_penalty` | number | Penalize repeated tokens. |
| `presence_penalty` | number | Penalize tokens already present. |
| `stop` | string or array | Stop sequences. |
| `response_format` | object | Set `{"type": "json_object"}` for JSON mode. |
| `tools` | array | Tool definitions for client-side tool calling. |
| `tool_choice` | string or object | `"auto"`, `"required"`, `"none"`, or `{"type":"function","function":{"name":"..."}}`. |

**Two modes:**

- **Agent mode** (no `tools`): The agent uses its pre-configured server-side tools (RAG, web search, etc.) and auto-executes them. The response contains the final text.
- **Client tool mode** (`tools` provided): Only the client-defined tools are available. The model returns `tool_calls` for the client to execute. Send results back with `role: "tool"` messages.

**Tool calling example:**

```python
# Step 1: send tools
response = client.chat.completions.create(
model="chat-agent",
messages=[{"role": "user", "content": "What's the weather?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
tool_choice="required",
)

# Step 2: execute tool and send result
tc = response.choices[0].message.tool_calls[0]
messages = [
{"role": "user", "content": "What's the weather?"},
response.choices[0].message.model_dump(),
{"role": "tool", "tool_call_id": tc.id, "content": '{"temp": 20}'},
]
final = client.chat.completions.create(
model="chat-agent", messages=messages, tools=tools
)
print(final.choices[0].message.content)
```

##### GET /api/v1/models

List available agents (models).

```json
{
"object": "list",
"data": [
{"id": "chat-agent", "object": "model", "owned_by": "default"},
{"id": "workflow-assistant", "object": "model", "owned_by": "default"}
]
}
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"esbuild"
],
"patchedDependencies": {
"convex-helpers@0.1.113": "patches/convex-helpers@0.1.113.patch"
"convex-helpers@0.1.114": "patches/convex-helpers@0.1.114.patch"
},
"overrides": {
"react-is": "19.2.5",
Expand Down
7 changes: 7 additions & 0 deletions services/platform/app/routes/docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ function ApiDocsPage() {
</main>

<style>{`
/* Override global overflow:clip so the docs page can scroll */
html:has(.swagger-ui-standalone),
html:has(.swagger-ui-standalone) body,
html:has(.swagger-ui-standalone) #root {
overflow: auto !important;
height: auto !important;
}
.swagger-ui-standalone .swagger-ui {
max-width: 1400px;
margin: 0 auto;
Expand Down
14 changes: 14 additions & 0 deletions services/platform/convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ import type * as lib_rls_wrappers_with_organization_rls from "../lib/rls/wrapper
import type * as lib_rls_wrappers_with_resource_rls from "../lib/rls/wrappers/with_resource_rls.js";
import type * as lib_shared_schemas_utils_json_value from "../lib/shared/schemas/utils/json_value.js";
import type * as lib_sops from "../lib/sops.js";
import type * as lib_strip_nulls from "../lib/strip_nulls.js";
import type * as lib_summarization_auto_summarize from "../lib/summarization/auto_summarize.js";
import type * as lib_summarization_index from "../lib/summarization/index.js";
import type * as lib_summarization_internal_actions from "../lib/summarization/internal_actions.js";
Expand Down Expand Up @@ -513,6 +514,12 @@ import type * as onedrive_upload_and_create_document_deps from "../onedrive/uplo
import type * as onedrive_upload_to_storage from "../onedrive/upload_to_storage.js";
import type * as onedrive_validators from "../onedrive/validators.js";
import type * as onedrive_with_microsoft_token from "../onedrive/with_microsoft_token.js";
import type * as openai_compat_http_actions from "../openai_compat/http_actions.js";
import type * as openai_compat_internal_actions from "../openai_compat/internal_actions.js";
import type * as openai_compat_internal_mutations from "../openai_compat/internal_mutations.js";
import type * as openai_compat_internal_queries from "../openai_compat/internal_queries.js";
import type * as openai_compat_response_format from "../openai_compat/response_format.js";
import type * as openai_compat_tool_conversion from "../openai_compat/tool_conversion.js";
import type * as organizations_actions from "../organizations/actions.js";
import type * as organizations_create_organization from "../organizations/create_organization.js";
import type * as organizations_delete_organization from "../organizations/delete_organization.js";
Expand Down Expand Up @@ -1312,6 +1319,7 @@ declare const fullApi: ApiFromModules<{
"lib/rls/wrappers/with_resource_rls": typeof lib_rls_wrappers_with_resource_rls;
"lib/shared/schemas/utils/json_value": typeof lib_shared_schemas_utils_json_value;
"lib/sops": typeof lib_sops;
"lib/strip_nulls": typeof lib_strip_nulls;
"lib/summarization/auto_summarize": typeof lib_summarization_auto_summarize;
"lib/summarization/index": typeof lib_summarization_index;
"lib/summarization/internal_actions": typeof lib_summarization_internal_actions;
Expand Down Expand Up @@ -1401,6 +1409,12 @@ declare const fullApi: ApiFromModules<{
"onedrive/upload_to_storage": typeof onedrive_upload_to_storage;
"onedrive/validators": typeof onedrive_validators;
"onedrive/with_microsoft_token": typeof onedrive_with_microsoft_token;
"openai_compat/http_actions": typeof openai_compat_http_actions;
"openai_compat/internal_actions": typeof openai_compat_internal_actions;
"openai_compat/internal_mutations": typeof openai_compat_internal_mutations;
"openai_compat/internal_queries": typeof openai_compat_internal_queries;
"openai_compat/response_format": typeof openai_compat_response_format;
"openai_compat/tool_conversion": typeof openai_compat_tool_conversion;
"organizations/actions": typeof organizations_actions;
"organizations/create_organization": typeof organizations_create_organization;
"organizations/delete_organization": typeof organizations_delete_organization;
Expand Down
2 changes: 2 additions & 0 deletions services/platform/convex/betterAuth/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { tables as generatedTables } from './generated_schema';
// Extend the generated tables with custom indexes
export const tables = {
...generatedTables,
// Add index on key field for efficient API key lookups
apikey: generatedTables.apikey.index('key', ['key']),
// Add custom index for [organizationId, userId] queries on member table
member: generatedTables.member.index('organizationId_userId', [
'organizationId',
Expand Down
31 changes: 31 additions & 0 deletions services/platform/convex/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import {
RateLimitExceededError,
} from './lib/rate_limiter/helpers';
import { toId } from './lib/type_cast_helpers';
import {
chatCompletionsHandler,
chatCompletionsOptionsHandler,
modelsListHandler,
modelsOptionsHandler,
} from './openai_compat/http_actions';
import {
ssoDiscoverHandler,
ssoAuthorizeHandler,
Expand Down Expand Up @@ -188,6 +194,31 @@ http.route({
handler: apiTriggerOptionsHandler,
});

// OpenAI-Compatible API Routes
http.route({
path: '/api/v1/chat/completions',
method: 'POST',
handler: chatCompletionsHandler,
});

http.route({
path: '/api/v1/chat/completions',
method: 'OPTIONS',
handler: chatCompletionsOptionsHandler,
});

http.route({
path: '/api/v1/models',
method: 'GET',
handler: modelsListHandler,
});

http.route({
path: '/api/v1/models',
method: 'OPTIONS',
handler: modelsOptionsHandler,
});

// API Gateway Routes - Handle /api/run/* paths with session cookie or API key authentication
http.route({
pathPrefix: '/api/run/',
Expand Down
3 changes: 3 additions & 0 deletions services/platform/convex/lib/agent_chat/internal_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export const runAgentGeneration = internalAction({
promptMessageId: v.optional(v.string()),
maxSteps: v.optional(v.number()),
deadlineMs: v.optional(v.number()),
generationParams: v.optional(v.any()),
},
handler: async (ctx, args) => {
const actionStartTime = Date.now();
Expand Down Expand Up @@ -162,6 +163,7 @@ export const runAgentGeneration = internalAction({
promptMessageId,
maxSteps,
deadlineMs,
generationParams,
} = args;

const agentType = narrowStringUnion(
Expand Down Expand Up @@ -360,6 +362,7 @@ export const runAgentGeneration = internalAction({
promptMessageId,
maxSteps,
deadlineMs,
generationParams,
},
);

Expand Down
9 changes: 8 additions & 1 deletion services/platform/convex/lib/agent_chat/start_agent_chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import {
computeDeduplicationState,
type AgentListMessagesResult,
} from '../message_deduplication';
import type { SerializableAgentConfig, AgentHooksConfig } from './types';
import type {
SerializableAgentConfig,
AgentHooksConfig,
GenerationParams,
} from './types';

const debugLog = createDebugLog('DEBUG_CHAT_AGENT', '[startAgentChat]');

Expand Down Expand Up @@ -81,6 +85,8 @@ export interface StartAgentChatArgs {
agentSlug?: string;
/** @deprecated Use agentSlug instead */
agentId?: Id<'agentBindings'>;
/** Optional per-request generation parameters (temperature, etc.) */
generationParams?: GenerationParams;
}

export interface StartAgentChatResult {
Expand Down Expand Up @@ -236,6 +242,7 @@ export async function startAgentChat(
additionalContext,
userContext,
deadlineMs,
generationParams: args.generationParams,
},
);

Expand Down
15 changes: 15 additions & 0 deletions services/platform/convex/lib/agent_chat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ export interface AgentRuntimeConfig {
* Arguments for runAgentGeneration action.
* These are the serialized arguments passed through scheduler.
*/
/**
* Optional LLM generation parameters (temperature, etc.).
* Only set fields that are explicitly provided; omit to use model defaults.
*/
export interface GenerationParams {
temperature?: number;
maxTokens?: number;
topP?: number;
frequencyPenalty?: number;
presencePenalty?: number;
stopSequences?: string[];
}

export interface RunAgentGenerationArgs {
agentType: string;
agentConfig: SerializableAgentConfig;
Expand All @@ -116,6 +129,8 @@ export interface RunAgentGenerationArgs {
streamId?: string;
promptMessageId?: string;
maxSteps?: number;
/** Optional per-request generation parameters from OpenAI compat endpoint */
generationParams?: GenerationParams;
}

/**
Expand Down
Loading
Loading