# Getting the most out of agents
Source: https://developers.datagrid.com/api-reference/agents/agent-best-practices
Practical guidance on instruction writing, mode selection, knowledge scoping, tool choice, and cost/latency trade-offs.
This page collects practical patterns for building reliable, cost-effective agents. Read [Getting started with Agents](/api-reference/agents/agents) first if you haven't set up an agent yet.
## Write effective system instructions
The three instruction fields serve distinct purposes — using them well is the single highest-leverage thing you can do for quality:
| Field | Use it for |
| ----------------- | ---------------------------------------------------------------------------------------------------------------- |
| `system_prompt` | **What** the agent does: its role, scope, and behavioral constraints. |
| `custom_prompt` | **How** it responds: tone, format, length, and style. |
| `planning_prompt` | **How** it approaches complex work: task decomposition, tool-use sequencing, or domain-specific reasoning steps. |
### Be specific about role and scope
Vague instructions produce inconsistent results. Give the agent a clear role and list what it should and should not do.
```python Python theme={null}
# Vague — the agent will guess
agent = client.agents.create(
name="HR Assistant",
system_prompt="Help employees with HR questions.",
)
# Better — role, scope, and a hard constraint are explicit
agent = client.agents.create(
name="HR Assistant",
system_prompt=(
"You are an HR assistant for Acme Corp employees. "
"Answer questions about benefits, leave policies, and onboarding. "
"Do not provide legal advice or discuss compensation for specific individuals."
),
custom_prompt="Use plain language. Keep answers under three paragraphs unless the user asks for more detail.",
)
```
```javascript JavaScript theme={null}
// Vague — the agent will guess
const agentVague = await client.agents.create({
name: 'HR Assistant',
system_prompt: 'Help employees with HR questions.',
});
// Better — role, scope, and a hard constraint are explicit
const agent = await client.agents.create({
name: 'HR Assistant',
system_prompt:
'You are an HR assistant for Acme Corp employees. ' +
'Answer questions about benefits, leave policies, and onboarding. ' +
'Do not provide legal advice or discuss compensation for specific individuals.',
custom_prompt:
'Use plain language. Keep answers under three paragraphs unless the user asks for more detail.',
});
```
### Use `planning_prompt` for multi-step work
For agents that orchestrate complex tasks — generating reports, extracting data across sources, or running multi-step workflows — `planning_prompt` tells the agent how to think before it acts:
```python Python theme={null}
agent = client.agents.create(
name="Data Analyst",
system_prompt="Analyze datasets and produce summaries with charts.",
planning_prompt=(
"Before answering, identify which datasets are relevant, then outline your analysis steps. "
"Run `data_analysis` on each dataset before synthesizing results. "
"Always create a chart if the answer is best communicated visually."
),
)
```
```javascript JavaScript theme={null}
const agent = await client.agents.create({
name: 'Data Analyst',
system_prompt: 'Analyze datasets and produce summaries with charts.',
planning_prompt:
'Before answering, identify which datasets are relevant, then outline your analysis steps. ' +
'Run `data_analysis` on each dataset before synthesizing results. ' +
'Always create a chart if the answer is best communicated visually.',
});
```
Iterate instructions the same way you iterate code: start minimal, run real prompts, and refine based on failures. The [generate → claim → create](/api-reference/agents/agents#generate-an-agent-from-natural-language) flow is useful for getting a first draft quickly.
## Choose the right mode
The mode controls how much planning and tool use happens for each turn. Pick the lightest mode that meets your needs — heavier modes take longer and cost more.
`magpie-2.5-flash` is deprecated and being withdrawn. Do not use it for new agents. For Ask behavior, use `chat_mode: "llm_router"`; for retrieval-focused workflows, use `magpie-1.1-flash`.
| Situation | Recommended approach |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Need full multi-step reasoning and tools | Execute tier: omit `agent_model` (defaults to `magpie-2.0`), or set `agent_model: "magpie-2.5"` for the latest beta |
| Need fast answers from a knowledge base | Extended tier: `config.agent_model: "magpie-1.1-flash"` — `semantic_search` only |
| Need tool-free LLM responses (structured output, simple Q\&A) | `config.agent_model: "llm-only"` |
| Reproducing in-app Ask behavior | `chat_mode: "llm_router"` (a routing mode, not a model) |
```python Python theme={null}
# Execute (default) — multi-step reasoning and all tools
response = client.converse(
prompt="Analyze last quarter's sales and flag any anomalies.",
agent_id=agent.id,
config={"agent_model": "magpie-2.0"},
)
# Extended — fast RAG, semantic_search only
response = client.converse(
prompt="What does the handbook say about parental leave?",
agent_id=agent.id,
config={
"agent_model": "magpie-1.1-flash",
"tools": ["semantic_search"],
},
)
# Direct LLM — no tools, lowest latency
response = client.converse(
prompt="Summarize this paragraph in one sentence.",
config={"agent_model": "llm-only"},
)
```
```javascript JavaScript theme={null}
// Execute (default) — multi-step reasoning and all tools
const executeResponse = await client.converse({
prompt: 'Analyze last quarter\'s sales and flag any anomalies.',
agent_id: agent.id,
config: { agent_model: 'magpie-2.0' },
});
// Extended — fast RAG, semantic_search only
const extendedResponse = await client.converse({
prompt: 'What does the handbook say about parental leave?',
agent_id: agent.id,
config: {
agent_model: 'magpie-1.1-flash',
tools: ['semantic_search'],
},
});
// Direct LLM — no tools, lowest latency
const llmResponse = await client.converse({
prompt: 'Summarize this paragraph in one sentence.',
config: { agent_model: 'llm-only' },
});
```
See [Converse modes](/api-reference/converse/modes) for the full mapping between `chat_mode`, `config.agent_model`, in-app labels, and tool availability.
## Scope knowledge for predictable behavior
By default an agent can reach **all knowledge** your organization has access to. This is convenient for exploration but problematic in production: the agent may pull in unrelated content, or expose knowledge that shouldn't inform the answer.
Set `corpus` explicitly to lock down what the agent reads.
```python Python theme={null}
# Without corpus — agent can reach all knowledge (unpredictable blast radius)
agent = client.agents.create(
name="Support Assistant",
system_prompt="Answer customer support questions.",
)
# With corpus — agent reads only the sources you specify
agent = client.agents.create(
name="Support Assistant",
system_prompt="Answer customer support questions.",
corpus=[
{"type": "knowledge", "knowledge_id": "kn_support_docs"},
{"type": "knowledge", "knowledge_id": "kn_product_faq"},
],
)
```
```javascript JavaScript theme={null}
// Without corpus — agent can reach all knowledge (unpredictable blast radius)
const agentBroad = await client.agents.create({
name: 'Support Assistant',
system_prompt: 'Answer customer support questions.',
});
// With corpus — agent reads only the sources you specify
const agent = await client.agents.create({
name: 'Support Assistant',
system_prompt: 'Answer customer support questions.',
corpus: [
{ type: 'knowledge', knowledge_id: 'kn_support_docs' },
{ type: 'knowledge', knowledge_id: 'kn_product_faq' },
],
});
```
You can also scope a single Converse turn without changing the stored agent by passing `config.corpus` on the request:
```python Python theme={null}
response = client.converse(
prompt="Summarize only the Q4 policy updates.",
agent_id=agent.id,
config={"corpus": [{"type": "knowledge", "knowledge_id": "kn_q4_policies"}]},
)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: 'Summarize only the Q4 policy updates.',
agent_id: agent.id,
config: { corpus: [{ type: 'knowledge', knowledge_id: 'kn_q4_policies' }] },
});
```
Each corpus item is either `{ "type": "knowledge", "knowledge_id": "kn_…" }` or `{ "type": "page", "page_id": "page_…" }`. Mixing both types in the same corpus is allowed. See [Knowledge and corpus](/api-reference/converse/knowledge-and-corpus) for more details.
## Select tools with least privilege
Giving an agent every tool makes it flexible but also increases the risk of side effects (writing data when the agent should only read) and can add latency from unnecessary tool-selection overhead.
**Start with the minimum tools needed and add more only when required.**
| Tool category | Tools | When to include |
| --------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Knowledge | `semantic_search`, `data_analysis`, `schema_info`, `table_info`, `agent_memory`, `charts` | Read-only agents: knowledge lookup and analysis |
| Data actions | `create_dataset`, `data_classification`, `data_extraction`, `image_detection`, `pdf_extraction` | When the agent needs to create or transform data |
| Web | `web_search`, `fetch_url`, `company_prospect_researcher`, `people_prospect_researcher` | When the agent needs live web information |
| Calendar / scheduling | `calendar`, `schedule_recurring_message_tool` | Only for agents that explicitly manage time |
### Use `disabled_tools` for fine-grained control
`disabled_tools` is applied *after* the `tools` list — useful when you want most of the default set but need to block specific tools:
```python Python theme={null}
# Allow the default tool set, but prevent write operations
agent = client.agents.create(
name="Read-Only Analyst",
system_prompt="Answer data questions. Do not create or modify datasets.",
disabled_tools=["create_dataset"],
)
```
```javascript JavaScript theme={null}
// Allow the default tool set, but prevent write operations
const agent = await client.agents.create({
name: 'Read-Only Analyst',
system_prompt: 'Answer data questions. Do not create or modify datasets.',
disabled_tools: ['create_dataset'],
});
```
`magpie-1.1-flash` (Extended tier) only supports `semantic_search`. `llm-only` supports no tools. Specifying unsupported tools for these models will cause the request to be rejected.
## Use structured outputs for integrations
When Converse is part of a pipeline — a webhook handler, a data-processing job, or a typed API — use `text.format` to guarantee the response matches your schema. This works for every mode and agent.
```python Python theme={null}
import json
response = client.converse(
prompt="Extract the contract start date and total value from this document.",
agent_id=agent.id,
text={
"format": {
"type": "object",
"properties": {
"start_date": {"type": "string", "description": "ISO 8601 date"},
"total_value": {"type": "number"},
"currency": {"type": "string"},
},
"required": ["start_date", "total_value", "currency"],
"additionalProperties": False,
}
},
)
data = json.loads(response.content[0].text)
print(data["start_date"], data["total_value"])
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: 'Extract the contract start date and total value from this document.',
agent_id: agent.id,
text: {
format: {
type: 'object',
properties: {
start_date: { type: 'string', description: 'ISO 8601 date' },
total_value: { type: 'number' },
currency: { type: 'string' },
},
required: ['start_date', 'total_value', 'currency'],
additionalProperties: false,
},
},
});
const data = JSON.parse(response.content[0].text);
console.log(data.start_date, data.total_value);
```
Schema design tips:
* Keep schemas flat where possible — deeply nested schemas are harder to validate and produce more hallucination surface.
* Mark only the fields you always need as `required`. Optional fields reduce failures on partial data.
* Use `additionalProperties: false` to prevent the model from adding undocumented keys.
See [Structured outputs](/api-reference/converse/structured-outputs) for the full reference.
## Iterate faster with the generate → claim → create flow
When starting a new agent, use `agents.generate` to get a first draft of instructions and tool selection from a natural-language description, then refine by hand.
```python Python theme={null}
# Describe what you need — no auth required, rate-limited to 5 requests/day per IP
generated = client.agents.generate(
prompt="An agent that reviews construction RFIs and flags missing information.",
)
# Claim the template to retrieve its configuration
template = client.agents.claim(claim_token=generated.claim_token)
# Review and refine before creating
agent = client.agents.create(
name=template.title,
system_prompt=template.config.prompt, # Refine this
custom_prompt=template.config.custom_prompt, # Refine this
tools=[t.tool for t in template.config.tools],
corpus=[{"type": "knowledge", "knowledge_id": "kn_rfi_docs"}], # Add explicit scoping
)
```
```javascript JavaScript theme={null}
// Describe what you need — no auth required, rate-limited to 5 requests/day per IP
const generated = await client.agents.generate({
prompt: 'An agent that reviews construction RFIs and flags missing information.',
});
// Claim the template to retrieve its configuration
const template = await client.agents.claim({ claim_token: generated.claim_token });
// Review and refine before creating
const agent = await client.agents.create({
name: template.title,
system_prompt: template.config.prompt, // Refine this
custom_prompt: template.config.custom_prompt, // Refine this
tools: template.config.tools.map((t) => t.tool),
corpus: [{ type: 'knowledge', knowledge_id: 'kn_rfi_docs' }], // Add explicit scoping
});
```
The `claim_token` expires after 7 days. The claim step retrieves the template — it does **not** create an agent. You still need to call `agents.create` to persist it.
After creating, use `agents.update` to iterate on instructions without destroying and re-creating the agent:
```python Python theme={null}
client.agents.update(
agent.id,
system_prompt="Refined instructions after testing...",
planning_prompt="Updated planning strategy...",
)
```
```javascript JavaScript theme={null}
await client.agents.update(agent.id, {
system_prompt: 'Refined instructions after testing...',
planning_prompt: 'Updated planning strategy...',
});
```
## Cost and latency guidance
Mode choice is the primary lever for both cost and latency:
The table below covers the currently recommended models. `magpie-2.5-flash` is deprecated and being withdrawn, so it is intentionally omitted.
| Model | Latency | Tools | Best for |
| ------------------ | ----------- | ---------------------- | -------------------------------------------------------------------- |
| `llm-only` | Lowest | None | Simple Q\&A, classification, structured extraction with no retrieval |
| `magpie-1.1-flash` | Low | `semantic_search` only | Fast knowledge-base lookups and RAG summarization |
| `magpie-2.0` | Medium–High | All | Multi-step reasoning, data analysis, actions |
| `magpie-2.5` | Medium–High | All | Latest Execute-tier capabilities (beta) |
See [Converse modes](/api-reference/converse/modes) for the authoritative tier table.
### Reduce streaming overhead
When streaming, set `include_steps: false` to suppress intermediate tool and reasoning events. This reduces event volume and is the right default for end-user-facing UIs that only need to show the final answer:
```python Python theme={null}
response = client.converse(
prompt="Summarize the latest report.",
agent_id=agent.id,
stream=True,
include_steps=False,
)
for event in response:
if event.event == "delta":
print(event.delta.text, end="", flush=True)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: 'Summarize the latest report.',
agent_id: agent.id,
stream: true,
include_steps: false,
});
for await (const event of response) {
if (event.event === 'delta') {
process.stdout.write(event.data.delta.text);
}
}
```
Use `stream: true` for any task that may take several seconds — it gives end-users visible progress instead of a silent wait, even if you're only interested in the final `end` event. See [Streaming](/api-reference/converse/streaming) for the full event shape.
### Other latency tips
* **Narrow `corpus`** — a tightly scoped corpus means the retrieval step searches less, returning results faster.
* **Narrow `tools`** — fewer tools means less overhead for the agent to decide which to call.
* **Cache `conversation_id`** — re-using a `conversation_id` gives the agent conversation history without re-sending it, keeping prompt size bounded over long sessions.
## Next steps
* [Getting started with Agents](/api-reference/agents/agents) — create and configure agents from scratch.
* [Getting started with Converse](/api-reference/converse/converse-getting-started) — the endpoint that runs them.
* [Converse modes](/api-reference/converse/modes) — full mode and model reference.
* [Knowledge and corpus](/api-reference/converse/knowledge-and-corpus) — corpus configuration deep-dive.
* [Structured outputs](/api-reference/converse/structured-outputs) — schema design and response parsing.
* [Streaming](/api-reference/converse/streaming) — full SSE event reference.
* [Agents API reference](/api-reference/agents/create-agent) — every field and endpoint.
# Getting started with Agents
Source: https://developers.datagrid.com/api-reference/agents/agents
Create, configure, and run AI agents with the Datagrid API.
An **agent** is a reusable configuration for answering prompts: a set of **instructions**, the **knowledge** it can draw on, and the **tools** it can use. Once created, you run an agent by passing its `agent_id` to [Converse](/api-reference/converse/converse).
This guide walks through creating an agent, configuring it, and running it. For the full field reference, see the [Agents API reference](/api-reference/agents/create-agent).
## Before you begin
Make sure you have an API key and an SDK installed. See the [Quickstart](/introduction/quickstart) if you haven't set those up yet.
## Create your first agent
Every field on `POST /agents` is optional, so the smallest useful agent is just a name and some instructions.
```python Python theme={null}
import os
from datagrid_ai import Datagrid
client = Datagrid(api_key=os.environ.get("DATAGRID_API_KEY"))
agent = client.agents.create(
name="Support Assistant",
system_prompt="You are a helpful support assistant. Answer concisely and cite sources.",
)
print(agent.id)
```
```javascript JavaScript theme={null}
import Datagrid from 'datagrid-ai';
const client = new Datagrid({ apiKey: process.env['DATAGRID_API_KEY'] });
const agent = await client.agents.create({
name: 'Support Assistant',
system_prompt: 'You are a helpful support assistant. Answer concisely and cite sources.',
});
console.log(agent.id);
```
The response is an `Agent` object. Keep its `id` — you'll pass it to Converse to run the agent.
## Configure instructions
Agents have three instruction fields, each with a distinct purpose:
| Field | Use it for |
| ----------------- | ----------------------------------------------------------------------- |
| `system_prompt` | The agent's role, scope, and behavior — **what** it should do. |
| `custom_prompt` | Style and formatting preferences — **how** responses should look. |
| `planning_prompt` | How the agent should break down and approach complex, multi-step tasks. |
```python Python theme={null}
agent = client.agents.create(
name="Support Assistant",
system_prompt="You are a support assistant for a construction software company.",
custom_prompt="Respond in short paragraphs. Use bullet points for steps.",
)
```
```javascript JavaScript theme={null}
const agent = await client.agents.create({
name: 'Support Assistant',
system_prompt: 'You are a support assistant for a construction software company.',
custom_prompt: 'Respond in short paragraphs. Use bullet points for steps.',
});
```
## Add knowledge with `corpus`
By default an agent can use **all knowledge** your organization or teamspace can access. To scope it to specific sources, pass a `corpus` array. Each item is either a knowledge base or a page.
```python Python theme={null}
agent = client.agents.create(
name="Policy Assistant",
system_prompt="Answer questions about company policy.",
corpus=[
{"type": "knowledge", "knowledge_id": "kn_abc123"},
{"type": "page", "page_id": "page_xyz789"},
],
)
```
```javascript JavaScript theme={null}
const agent = await client.agents.create({
name: 'Policy Assistant',
system_prompt: 'Answer questions about company policy.',
corpus: [
{ type: 'knowledge', knowledge_id: 'kn_abc123' },
{ type: 'page', page_id: 'page_xyz789' },
],
});
```
For predictable behavior and to avoid exposing more knowledge than intended, set `corpus` explicitly instead of relying on the all-knowledge default. See [Knowledge and corpus](/api-reference/converse/knowledge-and-corpus) for details.
## Enable tools
Tools let an agent do more than answer from knowledge — search the web, analyze data, extract from documents, and more. Pass a `tools` array of tool names. If you omit `tools`, a default set is used; passing `[]` disables all tools.
```python Python theme={null}
agent = client.agents.create(
name="Research Assistant",
system_prompt="Research topics and summarize findings.",
tools=["semantic_search", "web_search", "data_analysis"],
)
```
```javascript JavaScript theme={null}
const agent = await client.agents.create({
name: 'Research Assistant',
system_prompt: 'Research topics and summarize findings.',
tools: ['semantic_search', 'web_search', 'data_analysis'],
});
```
To see every available tool, use the [List tools](/api-reference/tools/list-tools) endpoint. You can also pass `disabled_tools` to remove specific tools from the default set.
## Generate an agent from natural language
Instead of authoring fields by hand, you can describe the agent you want and get a suggested configuration back. This is a three-step flow:
1. **Generate** a template from a prompt (`POST /agents/generate`).
2. **Claim** the template with the returned token to retrieve its config (`POST /agents/claim`).
3. **Create** the agent from the template fields (`POST /agents`).
`agents.generate` returns a `claim_token` that expires after 7 days. Claiming retrieves the template — it does **not** create the agent, so you still call `agents.create` to persist it.
```python Python theme={null}
# 1. Generate a template
generated = client.agents.generate(
prompt="An agent that reviews RFIs and flags missing information.",
)
# 2. Claim the template to retrieve its configuration
template = client.agents.claim(claim_token=generated.claim_token)
# 3. Create the agent from the template
agent = client.agents.create(
name=template.title,
system_prompt=template.config.prompt,
custom_prompt=template.config.custom_prompt,
tools=[t.tool for t in template.config.tools],
)
print(agent.id)
```
```javascript JavaScript theme={null}
// 1. Generate a template
const generated = await client.agents.generate({
prompt: 'An agent that reviews RFIs and flags missing information.',
});
// 2. Claim the template to retrieve its configuration
const template = await client.agents.claim({ claim_token: generated.claim_token });
// 3. Create the agent from the template
const agent = await client.agents.create({
name: template.title,
system_prompt: template.config.prompt,
custom_prompt: template.config.custom_prompt,
tools: template.config.tools.map((t) => t.tool),
});
console.log(agent.id);
```
The generate response lists tools as `{ "tool": "" }` objects, while `agents.create` expects tool **names** (strings). Map them as shown above (`t.tool` / `t => t.tool`).
## Run your agent
Pass the agent's `id` as `agent_id` on a Converse call. The agent's stored instructions, corpus, and tools are applied automatically.
```python Python theme={null}
response = client.converse(
prompt="What's our policy on remote work?",
agent_id=agent.id,
)
print(response.content[0].text)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: "What's our policy on remote work?",
agent_id: agent.id,
});
console.log(response.content[0].text);
```
You can override any part of the agent's configuration for a single turn by passing `config` on the Converse call — this does not change the stored agent.
```python Python theme={null}
response = client.converse(
prompt="Summarize only the Q4 policy updates.",
agent_id=agent.id,
config={"corpus": [{"type": "knowledge", "knowledge_id": "kn_q4_policies"}]},
)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: 'Summarize only the Q4 policy updates.',
agent_id: agent.id,
config: { corpus: [{ type: 'knowledge', knowledge_id: 'kn_q4_policies' }] },
});
```
## Update and delete
Update an agent with the same fields you used to create it (a partial update — only the fields you pass change):
```python Python theme={null}
client.agents.update(agent.id, system_prompt="You are a concise, friendly assistant.")
```
```javascript JavaScript theme={null}
await client.agents.update(agent.id, {
system_prompt: 'You are a concise, friendly assistant.',
});
```
Delete an agent when you no longer need it:
```python Python theme={null}
client.agents.delete(agent.id)
```
```javascript JavaScript theme={null}
await client.agents.delete(agent.id);
```
## Next steps
* [Getting started with Converse](/api-reference/converse/converse-getting-started) — the endpoint you use to run agents.
* [Knowledge and corpus](/api-reference/converse/knowledge-and-corpus) — scope what your agent can read.
* [Agents API reference](/api-reference/agents/create-agent) — every field and endpoint.
# Claim a generated agent template
Source: https://developers.datagrid.com/api-reference/agents/claim-a-generated-agent-template
post /agents/claim
Redeem a `claim_token` from a prior `POST /agents/generate` call to retrieve the generated agent template. The token is consumed (single-use). Use the returned template to pre-populate the agent creation dialog.
# Create agent
Source: https://developers.datagrid.com/api-reference/agents/create-agent
post /agents
Create a new agent
# Delete agent
Source: https://developers.datagrid.com/api-reference/agents/delete-agent
delete /agents/{agent_id}
Delete an agent
# Generate agent from natural language
Source: https://developers.datagrid.com/api-reference/agents/generate-agent-from-natural-language
post /agents/generate
Generate an AI agent configuration from a natural language description. Uses the same LLM-powered generation as the in-product agent builder. This is a public endpoint that does not require authentication. Rate limited to 5 requests per day per IP address. The response includes a `claim_token` that can be redeemed via `POST /agents/claim` after signing up to persist the agent.
# List agents
Source: https://developers.datagrid.com/api-reference/agents/list-agents
get /agents
List all agents for the authenticated organization
# Retrieve agent
Source: https://developers.datagrid.com/api-reference/agents/retrieve-agent
get /agents/{agent_id}
Get details of a specific agent
# Update agent
Source: https://developers.datagrid.com/api-reference/agents/update-agent
patch /agents/{agent_id}
Update an agent configuration
# Create automation
Source: https://developers.datagrid.com/api-reference/automations/create-automation
post /agents/{agent_id}/automations
Create a new `runAgent` automation for the specified agent. The automation will fire on the provided cron schedule (in the given IANA timezone) and run the agent with the supplied prompt. Schedules must fire at most once every 15 minutes — finer-grained expressions are rejected with a 400 error.
# Delete automation
Source: https://developers.datagrid.com/api-reference/automations/delete-automation
delete /agents/{agent_id}/automations/{automation_id}
Delete an automation. The automation is soft-deleted: it stops firing and no longer appears in list or retrieve responses, but its past run history is retained. The agent and its other automations are not affected. Scheduled runs that are already queued may still execute before the deletion propagates.
# List automations
Source: https://developers.datagrid.com/api-reference/automations/list-automations
get /agents/{agent_id}/automations
List all `runAgent` automations that belong to the specified agent. Returns automations in reverse-chronological order (newest first). Use the cursor pagination parameters (`limit`, `after`, `before`) to page through large result sets.
# Retrieve automation
Source: https://developers.datagrid.com/api-reference/automations/retrieve-automation
get /agents/{agent_id}/automations/{automation_id}
Get details of a specific automation belonging to an agent.
# Update automation
Source: https://developers.datagrid.com/api-reference/automations/update-automation
patch /agents/{agent_id}/automations/{automation_id}
Update an existing automation. All fields are optional — only supplied fields are changed. Updating `cron` or `timezone` triggers the same 15-minute minimum-interval validation as creation.
# Cancel batch prediction
Source: https://developers.datagrid.com/api-reference/batch-predictions/cancel-batch-prediction
post /batch-predictions/{batch_prediction_id}/cancel
Requests cancellation for a batch prediction that is still validating or in progress. A batch that is already `cancelling` or `cancelled` is returned unchanged.
# Create batch prediction
Source: https://developers.datagrid.com/api-reference/batch-predictions/create-batch-prediction
post /batch-predictions
Create a new asynchronous batch prediction job. The response returns immediately with a `validating` batch while Datagrid validates files and starts background processing. Supply an `Idempotency-Key` header to safely retry the same create request. The requested model must be available for the authenticated teamspace cloud provider.
# Batch prediction errors
Source: https://developers.datagrid.com/api-reference/batch-predictions/errors
Problem detail types returned by batch prediction endpoints and streamed result lines.
Batch prediction endpoints return RFC 9457 problem details (`application/problem+json`). The results stream also embeds the same shape inside each result line's `error` field when an individual item fails.
## Problem detail shape
```json theme={null}
{
"type": "https://api.datagrid.com/errors/validation_failed",
"title": "Validation Failed",
"status": 422,
"detail": "items must contain at least one item"
}
```
Validation errors include an `errors` array with JSON pointers into the request body. Item-level validation errors include `custom_id` when Datagrid can associate the issue with a submitted item.
```json theme={null}
{
"type": "https://api.datagrid.com/errors/validation_failed",
"title": "Validation Failed",
"status": 422,
"detail": "Duplicate custom_id 'drawing_001' in batch",
"errors": [
{
"pointer": "/items/1/custom_id",
"code": "duplicate_custom_id",
"message": "Duplicate custom_id 'drawing_001' in batch",
"custom_id": "drawing_001"
}
]
}
```
## Error type registry
| Type URI | Where it appears | Meaning |
| -------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `https://api.datagrid.com/errors/no_authentication` | Endpoint response | Missing, expired, or revoked API key. |
| `https://api.datagrid.com/errors/credit_exceeded` | Endpoint response | The teamspace does not have enough credits to enqueue or continue batch work. |
| `https://api.datagrid.com/errors/resource_not_found` | Endpoint response | The batch does not exist in the authenticated teamspace. |
| `https://api.datagrid.com/errors/conflict` | Endpoint response | The request conflicts with current state, such as reusing an idempotency key with a different request body or cancelling a batch that is already `completed`, `failed`, or `expired`. |
| `https://api.datagrid.com/errors/results_gone` | Endpoint response | Batch metadata still exists, but retained result lines have already been cleaned up. |
| `https://api.datagrid.com/errors/validation_failed` | Endpoint response or batch object | Synchronous request validation failed before the batch was accepted, or asynchronous batch validation failed after creation. |
| `https://api.datagrid.com/errors/batch_validation_failed` | Result line | Another item failed async validation, so this item was never processed. |
| `https://api.datagrid.com/errors/file_not_found` | Result line | The referenced file does not exist or is not accessible from the batch teamspace. |
| `https://api.datagrid.com/errors/unsupported_file_format` | Result line | The referenced file type cannot be processed by batch predictions, or a page reference was supplied for a non-paged file type. |
| `https://api.datagrid.com/errors/prediction_failed` | Result line | Model execution completed, but the item could not be returned as a valid prediction. |
| `https://api.datagrid.com/errors/batch_cancelled` | Result line | The item did not run because the batch was cancelled. |
| `https://api.datagrid.com/errors/batch_expired` | Result line | The item did not run before the batch completion window expired. |
| `https://api.datagrid.com/errors/batch_item_processing_failed` | Result line | Item processing failed due to an internal execution error. |
| `https://api.datagrid.com/errors/rate_limit_exceeded` | Endpoint response | The request was throttled or admission control rejected the create request. |
| `https://api.datagrid.com/errors/internal_server_error` | Endpoint response or result line | An unexpected server-side error occurred. |
## Example: rate-limited create request
```json theme={null}
{
"type": "https://api.datagrid.com/errors/rate_limit_exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Too many batch prediction requests are already in progress for this teamspace."
}
```
When present, inspect `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `RateLimit-Policy`, `RateLimit`, and `Retry-After` response headers before retrying.
## Example: cleaned results
```json theme={null}
{
"type": "https://api.datagrid.com/errors/results_gone",
"title": "Results Gone",
"status": 410,
"detail": "Batch prediction results are no longer available because they were deleted after the retention period."
}
```
## Example: streamed item failure
```json theme={null}
{
"object": "batch_prediction.result",
"batch_id": "bpred_123",
"custom_id": "drawing_002",
"status": "errored",
"output": null,
"error": {
"type": "https://api.datagrid.com/errors/file_not_found",
"title": "File Not Found",
"status": 422,
"detail": "File 'file_missing' does not exist or is not accessible from this teamspace."
}
}
```
# List batch predictions
Source: https://developers.datagrid.com/api-reference/batch-predictions/list-batch-predictions
get /batch-predictions
Returns batch predictions for the authenticated teamspace in reverse chronological order. Use `after` with `next_cursor` from the previous response to paginate.
# Batch Predictions overview
Source: https://developers.datagrid.com/api-reference/batch-predictions/overview
Create asynchronous batch extraction jobs, poll lifecycle state, and stream NDJSON results.
Batch Predictions lets you submit a shared prompt and output schema once, then process many files asynchronously through a single batch job. Each item references an existing Datagrid file and returns one NDJSON result line keyed by your `custom_id`.
## Recommended flow
1. Upload or identify the files you want to process with the Files API.
2. Call [Create batch prediction](/api-reference/batch-predictions/create-batch-prediction) with your shared `prompt`, `output_schema`, and item list.
3. Poll [Retrieve batch prediction](/api-reference/batch-predictions/retrieve-batch-prediction) or subscribe to a terminal-state webhook until the batch reaches `completed`, `failed`, `expired`, or `cancelled`.
4. Read [Retrieve batch prediction results](/api-reference/batch-predictions/retrieve-batch-prediction-results) and parse the NDJSON stream one line at a time.
5. If needed, stop pending work with [Cancel batch prediction](/api-reference/batch-predictions/cancel-batch-prediction).
## Webhooks
You can subscribe to terminal batch lifecycle events with [Webhooks](/api-reference/webhooks/overview). Batch prediction webhook event types are:
* `batch_prediction.completed`
* `batch_prediction.failed`
* `batch_prediction.expired`
* `batch_prediction.cancelled`
Webhook deliveries include the same batch prediction object returned by [Retrieve batch prediction](/api-reference/batch-predictions/retrieve-batch-prediction). Use the webhook as a notification that the batch reached a terminal state, then call the results endpoint when you need the full NDJSON result stream.
## Lifecycle
| Status | Meaning |
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
| `validating` | The batch was accepted and Datagrid is validating file access, file types, and page references. |
| `in_progress` | Items are actively being processed. |
| `finalizing` | All item work is done and Datagrid is finishing the batch. |
| `completed` | The batch finished successfully. Some individual items may still contain per-item errors in the results stream. |
| `failed` | Validation or processing failed before the batch could complete normally. |
| `cancelling` | A cancel request was accepted and remaining work is being stopped. |
| `cancelled` | The batch reached a terminal cancelled state. Completed items remain available in the results stream. |
| `expired` | The completion window elapsed before all items finished. Completed items remain available in the results stream. |
`completion_window` is currently fixed to `24h`.
## Request limits and validation
* A batch must contain between 1 and 5,000 items.
* The JSON request body must be no larger than 100 MiB.
* Each `custom_id` must be unique within the batch and 128 characters or fewer.
* `page` is optional, 1-indexed, and only valid for paged file formats.
* `metadata` can contain up to 16 string values. Keys must be 64 characters or fewer, and values must be 512 characters or fewer.
* `output_schema` must be a valid JSON Schema Draft 2020-12 object with root `type: "object"`. `$defs`, `$ref`, `allOf`, `anyOf`, `not`, `oneOf`, and `patternProperties` are not supported.
## Idempotency
`POST /v1/batch-predictions` accepts an optional `Idempotency-Key` header.
* Reusing the same key with the same request body replays the original response.
* Reusing the same key with a different request body returns `409 Conflict`.
* Idempotency records expire after 24 hours.
## Results format
The results endpoint returns `application/x-ndjson`, not a JSON array. Split on newlines and JSON parse each non-empty line independently.
```json theme={null}
{"object":"batch_prediction.result","batch_id":"bpred_123","custom_id":"drawing_001","status":"succeeded","output":{"architect_name":"Smith & Co."},"error":null}
{"object":"batch_prediction.result","batch_id":"bpred_123","custom_id":"drawing_002","status":"errored","output":null,"error":{"type":"https://api.datagrid.com/errors/prediction_failed","title":"Prediction Failed","status":422,"detail":"The model returned an invalid response."}}
```
Each line corresponds to one submitted item and preserves your `custom_id`. `output` is only populated when `status` is `succeeded`; `error` is populated when `status` is `errored`, `canceled`, or `expired`.
## Retention and cleanup
* Terminal batch metadata remains retrievable after processing.
* Retained result lines are currently eligible for cleanup 29 days after batch creation.
* When that happens, `results_url` becomes `null` and the results endpoint returns `410 Gone`.
## Related docs
* [Batch prediction error registry](/api-reference/batch-predictions/errors)
* [Rate limits](/api-reference/rate-limits)
# Retrieve batch prediction
Source: https://developers.datagrid.com/api-reference/batch-predictions/retrieve-batch-prediction
get /batch-predictions/{batch_prediction_id}
Retrieves a batch prediction by id. Terminal batches include a `results_url` until retained result lines are cleaned up.
# Retrieve batch prediction results
Source: https://developers.datagrid.com/api-reference/batch-predictions/retrieve-batch-prediction-results
get /batch-predictions/{batch_prediction_id}/results
Streams newline-delimited JSON (NDJSON) result lines for a terminal batch prediction. Read the response body line-by-line and JSON parse each non-empty line. Results are retained for a limited window after batch creation; after cleanup, this endpoint returns `410 Gone`.
# Add a message reaction
Source: https://developers.datagrid.com/api-reference/channels/add-a-message-reaction
post /channels/{channel_id}/messages/{message_id}/reactions
Adds the authenticated API user's reaction to a channel or thread message. The reacting user cannot be overridden: callers can only add their own reaction. Repeating the same reaction is idempotent. The caller must be a channel member. Returns the updated message.
The reaction must be an emoji available in the Datagrid emoji picker, supplied either as a shortcode (`:+1:`) or as the unicode character (`👍`). Shortcodes are resolved to the unicode character before storage, so a reaction added through the API groups together with the same emoji added in the app. Anything else is rejected with a 400.
# Add channel members
Source: https://developers.datagrid.com/api-reference/channels/add-channel-members
post /channels/{channel_id}/members
# Create channel
Source: https://developers.datagrid.com/api-reference/channels/create-channel
post /channels
Creates a shared public channel and adds the authenticated API user as a member.
# Create channel message
Source: https://developers.datagrid.com/api-reference/channels/create-channel-message
post /channels/{channel_id}/messages
Posts as the authenticated API user, who must be a channel member. Eligible channel agents may respond and consume credits.
# Create thread message
Source: https://developers.datagrid.com/api-reference/channels/create-thread-message
post /channels/{channel_id}/threads/{thread_id}/messages
Posts as the authenticated API user, who must be a channel member. Eligible channel agents may respond and consume credits.
# List channel members
Source: https://developers.datagrid.com/api-reference/channels/list-channel-members
get /channels/{channel_id}/members
# List channel messages
Source: https://developers.datagrid.com/api-reference/channels/list-channel-messages
get /channels/{channel_id}/messages
# List channel threads
Source: https://developers.datagrid.com/api-reference/channels/list-channel-threads
get /channels/{channel_id}/threads
# List channels
Source: https://developers.datagrid.com/api-reference/channels/list-channels
get /channels
Lists shared public named channels in the resolved teamspace. Private channels, direct messages, and personal chats are intentionally omitted. `q` filters channel names and does not search message content.
# List thread messages
Source: https://developers.datagrid.com/api-reference/channels/list-thread-messages
get /channels/{channel_id}/threads/{thread_id}/messages
# Remove a message reaction
Source: https://developers.datagrid.com/api-reference/channels/remove-a-message-reaction
delete /channels/{channel_id}/messages/{message_id}/reactions/{reaction}
Removes the authenticated API user's reaction from a channel or thread message. Callers cannot remove another user's reaction; a delete only clears the caller's own entry for that emoji. Removing a reaction that is not present is idempotent. The caller must be a channel member. Returns the updated message.
Accepts the same values as adding a reaction: a shortcode (`:+1:`) or the unicode character (`👍`), resolved to the same stored emoji.
# Remove channel member
Source: https://developers.datagrid.com/api-reference/channels/remove-channel-member
delete /channels/{channel_id}/members/{member_id}
# Retrieve channel
Source: https://developers.datagrid.com/api-reference/channels/retrieve-channel
get /channels/{channel_id}
# Retrieve channel thread
Source: https://developers.datagrid.com/api-reference/channels/retrieve-channel-thread
get /channels/{channel_id}/threads/{thread_id}
# Start a thread
Source: https://developers.datagrid.com/api-reference/channels/start-a-thread
post /channels/{channel_id}/messages/{message_id}/threads
Returns the existing thread when the message already has one.
# Update channel
Source: https://developers.datagrid.com/api-reference/channels/update-channel
patch /channels/{channel_id}
# Create connection provider
Source: https://developers.datagrid.com/api-reference/connection-providers/create-connection-provider
post /connection-providers
Create a new connection provider that specifies custom OAuth credentials for a connector. Verify that your OAuth app meets the connector's OAuth app settings requirements.
# Delete connection provider
Source: https://developers.datagrid.com/api-reference/connection-providers/delete-connection-provider
delete /connection-providers/{connection_provider_id}
Delete a connection provider.
# List connection providers
Source: https://developers.datagrid.com/api-reference/connection-providers/list-connection-providers
get /connection-providers
Returns the list of connection providers.
# Retrieve connection provider
Source: https://developers.datagrid.com/api-reference/connection-providers/retrieve-connection-provider
get /connection-providers/{connection_provider_id}
Retrieve a specific connection provider by ID.
# Update connection provider
Source: https://developers.datagrid.com/api-reference/connection-providers/update-connection-provider
patch /connection-providers/{connection_provider_id}
Update a connection provider.
# Create connection
Source: https://developers.datagrid.com/api-reference/connections/create-connection
post /connections
Creates a new connection to authenticate with a third-party service (like Google Drive, Hubspot, Dropbox, etc.) and returns a redirect URL for the connection authentication flow.
# Delete connection
Source: https://developers.datagrid.com/api-reference/connections/delete-connection
delete /connections/{connection_id}
Delete an authenticated connection to a third-party service.
# List connections
Source: https://developers.datagrid.com/api-reference/connections/list-connections
get /connections
Returns the list of authenticated connections to third-party services.
# Retrieve connection
Source: https://developers.datagrid.com/api-reference/connections/retrieve-connection
get /connections/{connection_id}
Retrieves details about an authenticated connection by id.
# Update connection
Source: https://developers.datagrid.com/api-reference/connections/update-connection
patch /connections/{connection_id}
Update a connection's attributes.
# Get connector health
Source: https://developers.datagrid.com/api-reference/connectors/get-connector-health
get /connectors/health
Returns health status for connectors configured in the current teamspace.
# List connectors
Source: https://developers.datagrid.com/api-reference/connectors/list-connectors
get /connectors
Returns the list of available connectors that can be used to connect to third-party services.
# Create conversation
Source: https://developers.datagrid.com/api-reference/conversations/create-conversation
post /conversations
Creates a new conversation.
# Delete conversation
Source: https://developers.datagrid.com/api-reference/conversations/delete-conversation
delete /conversations/{conversation_id}
Delete conversation.
# List conversations
Source: https://developers.datagrid.com/api-reference/conversations/list-conversations
get /conversations
Returns the list of conversations.
# List messages
Source: https://developers.datagrid.com/api-reference/conversations/list-messages
get /conversations/{conversation_id}/messages
Returns the list of messages in a conversation.
# Retrieve conversation
Source: https://developers.datagrid.com/api-reference/conversations/retrieve-conversation
get /conversations/{conversation_id}
Retrieves a conversation by id.
# Retrieve message
Source: https://developers.datagrid.com/api-reference/conversations/retrieve-message
get /conversations/{conversation_id}/messages/{message_id}
Retrieves a message by id.
# Update conversation
Source: https://developers.datagrid.com/api-reference/conversations/update-conversation
patch /conversations/{conversation_id}
Update a conversation's properties, such as assigned agents or name.
# Converse
Source: https://developers.datagrid.com/api-reference/converse/converse
post /converse
Converse with an AI Agent
# Getting started with Converse
Source: https://developers.datagrid.com/api-reference/converse/converse-getting-started
Make your first Converse call, then add modes, knowledge, streaming, structured outputs, file inputs, and MCP servers.
**Converse** is the primary endpoint for talking to Datagrid: send a `prompt`, get an AI response. A single endpoint serves every mode and feature, so you can start simple and layer in capabilities as you need them.
This page is a guided tour. For the full request and response schema, see the [Converse API reference](/api-reference/converse/converse).
## Your first call
```python Python theme={null}
import os
from datagrid_ai import Datagrid
client = Datagrid(api_key=os.environ.get("DATAGRID_API_KEY"))
response = client.converse(prompt="Hello world!")
print(response.content[0].text)
```
```javascript JavaScript theme={null}
import Datagrid from 'datagrid-ai';
const client = new Datagrid({ apiKey: process.env['DATAGRID_API_KEY'] });
const response = await client.converse({ prompt: 'Hello world!' });
console.log(response.content[0].text);
```
The response `content` is an array; the text of the reply is at `content[0].text`.
## Use an agent
To run a configured [agent](/api-reference/agents/agents), pass its `agent_id`. The agent's instructions, knowledge, and tools are applied automatically.
```python Python theme={null}
response = client.converse(
prompt="What's our policy on remote work?",
agent_id="",
)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: "What's our policy on remote work?",
agent_id: '',
});
```
The `config` object lets you override an agent's settings for a single turn without changing the stored agent. See [Getting started with Agents](/api-reference/agents/agents) for the full create-and-run flow.
## Choose a mode
Converse can answer with a fast LLM-first response, a lighter search-backed agent, or a full multi-step agent. You select this with `chat_mode` (and `config.agent_model`):
| `chat_mode` | In-app label | Best for |
| ------------- | ------------ | --------------------------------------------------- |
| `llm_router` | Ask | Fast, routed answers with agent context. |
| `light_agent` | Extended | Search-backed (RAG) responses. |
| `full_agent` | Execute | Multi-step planning and broad tool use. |
| `auto` | — | Let the router pick the agent and mode per message. |
See [Converse modes](/api-reference/converse/modes) for the full mapping between `chat_mode`, `config.agent_model`, and which tools each mode allows.
## Scope knowledge with corpus
By default the agent can use all knowledge in scope. Pass `config.corpus` to limit a request to specific knowledge bases or pages.
```python Python theme={null}
response = client.converse(
prompt="Summarize our Q4 policy updates",
config={
"corpus": [
{"type": "knowledge", "knowledge_id": "kn_abc123"},
{"type": "page", "page_id": "page_xyz789"},
]
},
)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: 'Summarize our Q4 policy updates',
config: {
corpus: [
{ type: 'knowledge', knowledge_id: 'kn_abc123' },
{ type: 'page', page_id: 'page_xyz789' },
],
},
});
```
More detail in [Knowledge and corpus](/api-reference/converse/knowledge-and-corpus).
## Stream responses
Set `stream: true` to receive the response incrementally as server-sent events — useful for responsive UIs on longer answers.
```python Python theme={null}
response = client.converse(prompt="Write a short summary", stream=True)
for event in response:
if event.event == "delta":
print(event.delta.text)
```
```javascript JavaScript theme={null}
const response = await client.converse({ prompt: 'Write a short summary', stream: true });
for await (const event of response) {
if (event.event === 'delta') {
console.log(event.data.delta.text);
}
}
```
See [Streaming](/api-reference/converse/streaming) for the full event flow (`start`, `delta`, `end`).
## Get structured output
Pass a JSON Schema in `text.format` to guarantee the response matches a schema. This works for every mode.
```python Python theme={null}
import json
response = client.converse(
prompt="What movie won best picture at the 2001 Oscars?",
text={
"format": {
"type": "object",
"properties": {
"name": {"type": "string"},
"director": {"type": "string"},
"release_year": {"type": "number"},
},
"required": ["name", "director", "release_year"],
"additionalProperties": False,
}
},
)
result = json.loads(response.content[0].text)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: 'What movie won best picture at the 2001 Oscars?',
text: {
format: {
type: 'object',
properties: {
name: { type: 'string' },
director: { type: 'string' },
release_year: { type: 'number' },
},
required: ['name', 'director', 'release_year'],
additionalProperties: false,
},
},
});
const result = JSON.parse(response.content[0].text);
```
More in [Structured outputs](/api-reference/converse/structured-outputs).
## Add file inputs
Upload a file, then reference it in a structured prompt to ask questions about documents, PDFs, or images.
```python Python theme={null}
with open("report.pdf", "rb") as file:
uploaded = client.files.create(file=file)
response = client.converse(
prompt=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "Summarize this document"},
{"type": "input_file", "file_id": uploaded.id},
],
}
]
)
```
```javascript JavaScript theme={null}
import fs from 'fs';
const uploaded = await client.files.create({ file: fs.createReadStream('report.pdf') });
const response = await client.converse({
prompt: [
{
role: 'user',
content: [
{ type: 'input_text', text: 'Summarize this document' },
{ type: 'input_file', file_id: uploaded.id },
],
},
],
});
```
See [File inputs](/api-reference/converse/file-inputs) for supported file types.
## Continue a conversation
Pass a `conversation_id` to keep context across turns. The first call returns one (or you can create one explicitly via the [Conversations API](/api-reference/conversations/create-conversation)).
```python Python theme={null}
first = client.converse(prompt="What's the capital of France?")
follow_up = client.converse(
prompt="What's its population?",
conversation_id=first.conversation_id,
)
```
```javascript JavaScript theme={null}
const first = await client.converse({ prompt: "What's the capital of France?" });
const followUp = await client.converse({
prompt: "What's its population?",
conversation_id: first.conversation_id,
});
```
## More capabilities
* [MCP servers](/api-reference/converse/mcp-servers) — connect external tools via the Model Context Protocol (Beta).
* [Rate limits](/api-reference/rate-limits) — limits, headers, and retry guidance.
## Next steps
* [Getting started with Agents](/api-reference/agents/agents) — create reusable agents to run via Converse.
* [Converse modes](/api-reference/converse/modes) — pick the right mode for each task.
# File Inputs
Source: https://developers.datagrid.com/api-reference/converse/file-inputs
Learn how to use and files as inputs to the converse API.
Files enable you to pass documents, PDFs, images, and other file types to the converse API. This guide shows you how to upload files and include them in your conversations.
```python Python theme={null}
import os
from datagrid_ai import Datagrid
client = Datagrid(api_key=os.environ.get("DATAGRID_API_KEY"))
# Step 1: Upload a file
with open("report.pdf", "rb") as file:
uploaded_file = client.files.create(file=file)
# Step 2: Include the file in a conversation
response = client.converse(
prompt=[
{
"role": "user",
"content": [
{"type": "input_text", "text": "Summarize this document"},
{"type": "input_file", "file_id": uploaded_file.id}
]
}
]
)
print(response.content[0].text)
```
```javascript JavaScript theme={null}
import Datagrid from 'datagrid-ai';
import fs from 'fs';
const client = new Datagrid({
apiKey: process.env['DATAGRID_API_KEY']
});
// Step 1: Upload a file
const uploadedFile = await client.files.create({
file: fs.createReadStream('report.pdf')
});
// Step 2: Include the file in a conversation
const response = await client.converse({
prompt: [
{
role: 'user',
content: [
{ type: 'input_text', text: 'Summarize this document' },
{ type: 'input_file', file_id: uploadedFile.id }
]
}
]
});
console.log(response.content[0].text);
```
# Knowledge and corpus
Source: https://developers.datagrid.com/api-reference/converse/knowledge-and-corpus
Control which knowledge your agent uses when answering questions.
Your agent can use **knowledge** (and **pages**) to answer questions. You choose whether it uses all available knowledge or only the sources you specify.
## Default: use all knowledge
If you don't set `corpus` on the agent or in the request, the agent can use **all knowledge your organization or teamspace can access**. You don't need to pass a list—just call converse and the agent has access to everything in scope.
You can set `corpus` when you create or update an agent, or override it per request in the converse `config`.
## Scope to specific knowledge
To limit the agent to certain knowledge bases or pages, pass a `corpus` array. Each item is either a knowledge base or a page. This works in the agent config or in `config` on a converse call.
```python Python theme={null}
import os
from datagrid_ai import Datagrid
client = Datagrid(api_key=os.environ.get("DATAGRID_API_KEY"))
# Use only these knowledge sources for this request
response = client.converse(
prompt="Summarize our Q4 policy updates",
config={
"corpus": [
{"type": "knowledge", "knowledge_id": "kn_abc123"},
{"type": "page", "page_id": "page_xyz789"}
]
}
)
```
```javascript JavaScript theme={null}
import Datagrid from 'datagrid-ai';
const client = new Datagrid({
apiKey: process.env['DATAGRID_API_KEY']
});
// Use only these knowledge sources for this request
const response = await client.converse({
prompt: 'Summarize our Q4 policy updates',
config: {
corpus: [
{ type: 'knowledge', knowledge_id: 'kn_abc123' },
{ type: 'page', page_id: 'page_xyz789' }
]
}
});
```
For situations that call for predictable behavior and to avoid exposing more knowledge than intended, set `corpus` explicitly instead of relying on the default. That way the agent only uses the knowledge you specify.
# MCP Servers (Beta)
Source: https://developers.datagrid.com/api-reference/converse/mcp-servers
Connect external tools to your agent using the Model Context Protocol.
**Beta**: This feature is in beta. The API schema may change as we iterate on the design.
[MCP (Model Context Protocol)](https://modelcontextprotocol.io/) is an open standard that enables AI agents to interact with external tools and services. By passing MCP server configurations in your converse requests, you can extend the agent's capabilities with custom tools.
MCP servers are configured per-request. The agent will discover available tools from each server and use them as needed to complete the user's request.
For custom MCP usage, prefer **Execute** mode (`chat_mode: "full_agent"`). Other modes may limit or skip tool execution depending on routing/model behavior.
## Basic Usage
```python Python theme={null}
import os
from datagrid_ai import Datagrid
client = Datagrid(api_key=os.environ.get("DATAGRID_API_KEY"))
response = client.converse(
prompt="Tell me about my projects",
chat_mode="full_agent",
config={
"mcp_servers": [
{
"type": "inline_mcp",
"server_label": "project-api",
"server_url": "https://mcp.mycompany.com",
"server_description": "Project management API",
"authorization": "Bearer your-api-token"
}
]
}
)
print(response.content[0].text)
```
```javascript JavaScript theme={null}
import Datagrid from 'datagrid-ai';
const client = new Datagrid({
apiKey: process.env['DATAGRID_API_KEY']
});
const response = await client.converse({
prompt: 'Tell me about my projects',
chat_mode: 'full_agent',
config: {
mcp_servers: [
{
type: 'inline_mcp',
server_label: 'project-api',
server_url: 'https://mcp.mycompany.com',
server_description: 'Project management API',
authorization: 'Bearer your-api-token'
}
]
}
});
console.log(response.content[0].text);
```
```bash curl theme={null}
curl -X POST https://api.datagrid.com/v1/converse \
-H "Authorization: Bearer $DATAGRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Tell me about my projects",
"chat_mode": "full_agent",
"config": {
"mcp_servers": [{
"type": "inline_mcp",
"server_label": "project-api",
"server_url": "https://mcp.mycompany.com",
"server_description": "Project management API",
"authorization": "Bearer your-api-token"
}]
}
}'
```
## Configuration Options
| Field | Type | Required | Description |
| -------------------- | ------ | -------- | --------------------------------------------------------------------------------------------- |
| `type` | string | Yes | Must be `"inline_mcp"` for server configs passed in the request |
| `server_label` | string | Yes | Unique identifier for the server (max 64 characters). Used for tool namespacing. |
| `server_url` | string | Yes | HTTPS URL of the MCP streamable HTTP endpoint |
| `server_description` | string | No | Description of what the server provides (max 500 characters) |
| `authorization` | string | No | Value sent in the `Authorization` header when calling the MCP server (e.g., `"Bearer token"`) |
## Multiple Servers
You can connect multiple MCP servers in a single request. Each server's tools will be available to the agent:
```python Python theme={null}
response = client.converse(
prompt="Get the weather forecast and add it to my calendar",
config={
"mcp_servers": [
{
"type": "inline_mcp",
"server_label": "weather",
"server_url": "https://mcp.example.com/weather",
"authorization": "Bearer weather-token"
},
{
"type": "inline_mcp",
"server_label": "calendar",
"server_url": "https://mcp.example.com/calendar",
"authorization": "Bearer calendar-token"
}
]
}
)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: 'Get the weather forecast and add it to my calendar',
config: {
mcp_servers: [
{
type: 'inline_mcp',
server_label: 'weather',
server_url: 'https://mcp.example.com/weather',
authorization: 'Bearer weather-token'
},
{
type: 'inline_mcp',
server_label: 'calendar',
server_url: 'https://mcp.example.com/calendar',
authorization: 'Bearer calendar-token'
}
]
}
});
```
## How It Works
When you pass an MCP server in a converse request, Datagrid handles the full [MCP lifecycle](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle) automatically:
1. **Initialization**: Datagrid sends `initialize` to negotiate protocol version and capabilities, then confirms with `notifications/initialized`
2. **Tool Discovery**: Datagrid calls `tools/list` to discover available tools, with `MCP-Session-Id` and `MCP-Protocol-Version` headers
3. **Tool Execution**: When the agent decides to use a tool, Datagrid calls `tools/call` with the appropriate parameters and session headers
4. **Session Recovery**: If a session expires, Datagrid automatically re-initializes and retries the request
5. **Credential Isolation**: Authorization values are scoped per server and never shared between servers
References:
* [MCP Lifecycle](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle)
* [MCP Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports)
## Security
* **HTTPS Required**: All MCP server URLs must use HTTPS
* **SSRF Protection**: Requests to private IP addresses, localhost, and internal domains are blocked
* **Credential Isolation**: Authorization tokens are isolated per-server and never shared between servers
* **Ephemeral**: Server configurations are not persisted - they only exist for the duration of the request
## Building MCP Servers
To create an MCP server compatible with Datagrid, implement the [Model Context Protocol specification](https://modelcontextprotocol.io/specification) using [streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports). Your server must handle the following methods:
* `initialize` - Negotiates protocol version and capabilities. Return `MCP-Session-Id` in the response header.
* `notifications/initialized` - Accepts the initialization completion notification
* `tools/list` - Returns available tools and their [JSON Schema](https://json-schema.org/) definitions
* `tools/call` - Executes a tool and returns the result
All requests after `initialize` will include `MCP-Session-Id` and `MCP-Protocol-Version` headers.
Frameworks like [FastMCP](https://github.com/jlowin/fastmcp) and the [official MCP SDK](https://modelcontextprotocol.io/docs/develop/build-server) handle `initialize`, session management, and transport automatically. See [Build an MCP Server](https://modelcontextprotocol.io/docs/develop/build-server) to get started.
# Converse Modes
Source: https://developers.datagrid.com/api-reference/converse/modes
Map in-app Ask / Extended / Execute to config.agent_model and chat_mode on Converse.
In the **Datagrid web app**, chat uses three modes—**Ask**, **Extended**, and **Execute**—which correspond to the Converse **`chat_mode`** field (`llm_router`, `light_agent`, `full_agent`). This page documents **`config.agent_model`**, which selects the model implementation and which tools are allowed.
**Default mode:** New chats and omitted/default `chat_mode` run as **Execute** (`full_agent`) with the default **`agent_model: magpie-2.5`**, unless you explicitly set a different supported `agent_model`.
**Auto mode:** `chat_mode: auto` is still accepted for compatibility, but currently runs as **Execute** (`full_agent`). It does not predict Ask, Extended, or Execute per message.
**Structured outputs:** For any **`agent_model`**, you can request JSON that matches a schema by passing **`text.format`** (JSON Schema) on the Converse request. The same mechanism applies across modes; tool limits below still apply.
**Important:** The **Ask** mode in the web app is the **`llm_router`** `chat_mode`, not `magpie-1.1-flash`. The **`magpie-1.1-flash`** model aligns with **Extended** in the app (search-backed, lighter agent). Use both `chat_mode` and `agent_model` when reproducing in-app behavior from the API.
## Ask (in-app; `chat_mode: llm_router`)
**Ask** is a **product / routing mode**, not a value of **`config.agent_model`**. In the web app it is selected with **`chat_mode: llm_router`**.
**What it is good for:** Fast, LLM-forward answers when the product has already chosen an agent for instructions and context. The server resolves routing (including agent context), then follows an LLM-focused path rather than full **Execute**-style multi-tool planning.
**How it differs from `llm-only`:** **`llm-only`** is an explicit **`agent_model`** on the Converse API: you get the direct LLM tier with **no tool execution** and predictable behavior from that string alone. **`llm_router`** instead means “use the in-app Ask routing rules” (agent resolution, mode refinement, default models) on top of whatever **`agent_model`** and **`agent_id`** you pass. To mirror **Ask** from the API, set **`chat_mode`** to **`llm_router`** and the same agent identifiers you use in the app; do not assume **`agent_model: llm-only`** alone reproduces **Ask**, and do not confuse **`llm_router`** with **Extended** (**`light_agent`** / **`magpie-1.1-flash`**).
Structured outputs for turns that use **`llm_router`** still use **`text.format`** like any other Converse call.
## Execute (full agent models)
Full agent with multi-step planning, reasoning, and broad tool execution. Matches **Execute** in the Datagrid web app (`chat_mode: full_agent`). This is the default for new chats and omitted/default `chat_mode`; when `agent_model` is omitted, Execute uses `magpie-2.5`.
| `agent_model` | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `magpie-2.0` | Legacy full-agent model with proactive planning and reasoning. |
| `magpie-2.5` | **Default.** Latest full-agent model — faster, more adaptable, and built to handle a broader range of real-world tasks. |
| `magpie-1.1` | Previous-generation full agent model. |
All tools listed for agents are available for **Execute**-tier models. Structured outputs use `text.format` (see [Structured outputs](./structured-outputs)).
```python Python theme={null}
response = datagrid.converse(
prompt="Analyze my sales data and create a summary report",
config={
"agent_model": "magpie-2.5",
"tools": ["data_analysis", "semantic_search", "create_dataset"]
}
)
```
```javascript JavaScript theme={null}
const response = await datagrid.converse({
prompt: "Analyze my sales data and create a summary report",
config: {
agent_model: "magpie-2.5",
tools: ["data_analysis", "semantic_search", "create_dataset"],
},
});
```
## Extended (search-focused agent)
Lightweight turns optimized for RAG (retrieval-augmented generation). Matches **Extended** in the Datagrid web app (`chat_mode: light_agent`). Faster than **Execute**-tier models with lower latency.
| `agent_model` | Description |
| ------------------ | --------------------------------------------------------- |
| `magpie-1.1-flash` | Fast model that only supports the `semantic_search` tool. |
Only the `semantic_search` tool is supported for **`magpie-1.1-flash`**. Requests specifying other tools will be rejected. Structured outputs use **`text.format`** like other models (see [Structured outputs](./structured-outputs)).
```python Python theme={null}
response = datagrid.converse(
prompt="What is our refund policy?",
config={
"agent_model": "magpie-1.1-flash",
"tools": ["semantic_search"]
}
)
```
```javascript JavaScript theme={null}
const response = await datagrid.converse({
prompt: "What is our refund policy?",
config: {
agent_model: "magpie-1.1-flash",
tools: ["semantic_search"],
},
});
```
## Direct LLM (`llm-only`)
Direct LLM response with no planning or tool execution. Lowest latency, best for simple conversational or structured JSON answers that do not need retrieval or actions.
| `agent_model` | Description |
| ------------- | ------------------------------------------- |
| `llm-only` | Direct LLM conversation with no tool calls. |
**`llm-only`** is the API’s explicit tool-free model key. It is **not** the same thing as selecting **Ask** in the web app: **Ask** is **`chat_mode: llm_router`** (see the **Ask** section at the top of this page).
No tools are executed for **`llm-only`**. Requests specifying tools will be rejected. Structured outputs use **`text.format`** (see [Structured outputs](./structured-outputs)).
```python Python theme={null}
response = datagrid.converse(
prompt="Summarize the key differences between GAAP and IFRS",
config={
"agent_model": "llm-only"
}
)
```
```javascript JavaScript theme={null}
const response = await datagrid.converse({
prompt: "Summarize the key differences between GAAP and IFRS",
config: {
agent_model: "llm-only",
},
});
```
## Choosing a mode
| Tier (in-app label) | API surface | Use when | Latency | Tools | Structured outputs |
| ------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------- | --------------------------- | ------------------ |
| **Ask** | **`chat_mode: llm_router`** (not an `agent_model` string) | Routed LLM-first answers with agent context in the product | Lower | Per routing / resolved path | Yes |
| **Execute** | **Default**; `chat_mode: full_agent`; `magpie-2.5` when `agent_model` is omitted | Multi-step reasoning, tool calls, or data analysis | Higher | All | Yes |
| **Auto** | `chat_mode: auto` | Compatibility value that currently runs as **Execute** (`full_agent`) | Higher | All | Yes |
| **Extended** | `magpie-1.1-flash` | Fast answers from knowledge bases (RAG) with `semantic_search` only | Medium | `semantic_search` only | Yes |
| **Direct LLM** | `llm-only` | Tool-free conversational or structured JSON from a fixed model key | Lowest | None | Yes |
When `config.agent_model` is omitted, the API defaults to `magpie-2.5` (**Execute** tier).
# Streaming
Source: https://developers.datagrid.com/api-reference/converse/streaming
Stream a conversation with an AI Agent
When conversing, you can set `"stream": true` to incrementally stream the response using [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent%5Fevents/Using%5Fserver-sent%5Fevents) (SSE).
## Streaming with SDKs
Both the [Python](https://github.com/DatagridAI/datagrid-python) and [Typescript/Javascript](https://github.com/DatagridAI/datagrid-node) SDKs provide convenient APIs to stream responses.
```python Python theme={null}
from datagrid_ai import Datagrid
datagrid = Datagrid()
response = datagrid.converse(
prompt="prompt",
stream=True,
)
for event in response:
if (event.event == "delta"):
print(event.delta.text)
```
```javascript JavaScript theme={null}
import Datagrid from "datagrid-ai";
const datagrid = new Datagrid();
const response = await datagrid.converse({ prompt: "prompt", stream: true });
for await (const event of response) {
if (event.event === "delta") {
console.log(event.data.delta.text);
}
}
```
## Event types
Each server-sent event includes a named event type and associated JSON data. Each event will use an SSE event name (e.g., event: delta), and include the matching event type in its data.
Each stream uses the following event flow:
1. `start`: Indicates the start of the conversation and contains the `conversation_id` and `agent_id` used to answer the prompt.
2. `delta`: contains the `delta` object with the changes to the message.
3. `end`: Indicates the end of the conversation.
# Structured Outputs
Source: https://developers.datagrid.com/api-reference/converse/structured-outputs
Ensure responses adhere to a JSON schema.
JSON is one of the most common formats used for data exchange between applications.
Structured Outputs is a feature that guarantees the model's responses will always match your provided [JSON Schema](https://json-schema.org/overview/what-is-jsonschema). This means you can rely on it to include all required fields and avoid generating invalid enum values or incorrect formats.
You can request Datagrid to output a structured response by passing in a JSON Schema into the `text.format` field. **Structured outputs work for every Converse `agent_model` and `chat_mode`** when you supply `text.format`; differences between modes are about **tools and routing**, not about whether schema-constrained JSON is available.
```python Python theme={null}
from datagrid_ai import Datagrid
import json
datagrid = Datagrid()
example_json_schema = {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the movie"
},
"director": {
"type": "string",
"description": "The director of the movie"
},
"release_year": {
"type": "number",
"description": "The year the movie was released"
}
},
"required": ["name", "director", "release_year"],
"additionalProperties": False
}
response = datagrid.converse(
prompt="What movie won best picture at the 2001 Oscars?",
text={"format": example_json_schema}
)
# Structured output is returned as a `json` content item (already parsed).
json_part = next((part for part in response["content"] if part["type"] == "json"), None)
structured_response = json_part["json"] if json_part else None
# => { "name": "Gladiator", "director": "Ridley Scott", "release_year": 2000 }
```
```javascript JavaScript theme={null}
import Datagrid from "datagrid-ai";
const datagrid = new Datagrid();
const movieJsonSchema = {
type: "object",
properties: {
name: {
type: "string",
description: "The name of the movie",
},
director: {
type: "string",
description: "The director of the movie",
},
release_year: {
type: "number",
description: "The year the movie was released",
},
},
required: ["name", "director", "release_year"],
additionalProperties: false,
};
const response = await datagrid.converse({
prompt: "What movie won best picture at the 2001 Oscars?",
text: { format: movieJsonSchema },
});
// Structured output is returned as a `json` content item (already parsed).
const jsonPart = response.content.find((part) => part.type === "json");
const structuredResponse = jsonPart?.json;
// => { name: "Gladiator", director: "Ridley Scott", release_year: 2000 }
```
Libraries such as [Pydantic](https://pypi.org/project/pydantic/) (Python) or [Zod](https://www.npmjs.com/package/zod) (JavaScript) are recommended when manipulating JSON Schemas.
Structured output uses the `text.format` field on the Converse request: you pass a JSON Schema, and the model returns JSON that follows it. Use [Modes](./modes) for how **Ask** (`llm_router`), **Extended**, **Execute**, and **`llm-only`** differ for tools and latency.
## Validation and error handling
Structured outputs are **enforced server-side**. After the agent generates a response, Datagrid validates it against your `text.format` schema before returning it:
* **On success**, the validated value is returned as a `json` content item with the value already parsed — you don't need `JSON.parse` / `json.loads`. A JSON-string `text` content item is also returned, so existing integrations that read `content[0].text` keep working.
* **On failure** — when the agent cannot produce schema-valid JSON — Datagrid does not return partial or non-conforming data, and no `json` content item is included. It returns a deterministic error object in a `text` content item:
```json theme={null}
{
"error": {
"code": "structured_output_generation_failed",
"message": "The agent did not produce valid JSON for this structured output request."
}
}
```
Read the structured result from the `json` content item (or parse the JSON-string `content[0].text`), and treat `structured_output_generation_failed` as a signal to retry the request or relax your schema.
# Get credits
Source: https://developers.datagrid.com/api-reference/credits/get-credits
get /organization/credits
Summarise the credits in your account for the current billing period.
# Create data view
Source: https://developers.datagrid.com/api-reference/data-views/create-data-view
post /data-views
Creates a new data view for a knowledge source, providing controlled access through a service account. This endpoint requires credits and meters setup work based on the number of warehouse operations performed for the request.
For a complete guide on setting up Data Views including creating knowledge, service accounts, and obtaining BigQuery credentials, see the [Creating Data Views Introduction](/introduction/data-views).
# Create service account
Source: https://developers.datagrid.com/api-reference/data-views/create-service-account
post /data-views/service-accounts
Creates a service account for accessing data views. Only one service account per teamspace is allowed.
# Delete data view
Source: https://developers.datagrid.com/api-reference/data-views/delete-data-view
delete /data-views/{data_view_id}
Removes a data view.
# Delete service account
Source: https://developers.datagrid.com/api-reference/data-views/delete-service-account
delete /data-views/service-accounts/{service_account_id}
Removes a service account and all associated data views.
# Get service account credentials
Source: https://developers.datagrid.com/api-reference/data-views/get-service-account-credentials
get /data-views/service-accounts/{service_account_id}/credentials
Retrieves the credentials (private key) for a service account.
# List data views
Source: https://developers.datagrid.com/api-reference/data-views/list-data-views
get /data-views
Returns the list of data views for a service account.
# List service accounts
Source: https://developers.datagrid.com/api-reference/data-views/list-service-accounts
get /data-views/service-accounts
Returns the list of service accounts for your teamspace. Only one service account per teamspace is allowed at this time.
# Create event trigger
Source: https://developers.datagrid.com/api-reference/event-triggers/create-event-trigger
post /agents/{agent_id}/event-triggers
Create an event trigger for the specified agent so it runs automatically when the configured provider event fires. Prerequisite gaps that do not prevent the trigger from being saved (e.g. a missing Procore company/project mapping or credentials) are returned in the `warnings` array on the response rather than failing the request. An agent may only have one event trigger — attempting to create a second is rejected with a 409 error.
# Delete event trigger
Source: https://developers.datagrid.com/api-reference/event-triggers/delete-event-trigger
delete /agents/{agent_id}/event-triggers/{trigger_id}
Delete an event trigger from the specified agent. This unregisters the underlying provider webhook subscription and stops the agent from running in response to the event.
# List event triggers
Source: https://developers.datagrid.com/api-reference/event-triggers/list-event-triggers
get /agents/{agent_id}/event-triggers
List the App Event automations (event triggers) configured for the specified agent. An event trigger fires the agent in response to an external provider event (e.g. a Procore webhook such as `Tasks:create`). An agent has at most one event trigger, so the returned list contains zero or one item and `has_more` is always `false`.
# Event Triggers overview
Source: https://developers.datagrid.com/api-reference/event-triggers/overview
Run an agent automatically when an external provider event fires, such as a Procore webhook.
An event trigger (an "App Event automation") runs an agent automatically in response to an external provider event — for example, a Procore webhook such as `Tasks:create`. Instead of invoking the agent yourself, you register a trigger once and Datagrid runs the agent each time the configured event fires.
## Recommended flow
1. Identify the agent you want to run and the provider event that should fire it.
2. Call [Create event trigger](/api-reference/event-triggers/create-event-trigger) with the `trigger_source`, `trigger_event`, and `enabled` state.
3. Inspect the `warnings` array on the response for any prerequisite gaps (see [Warnings](#warnings)).
4. Use [List event triggers](/api-reference/event-triggers/list-event-triggers) to read the current trigger, [Update event trigger](/api-reference/event-triggers/update-event-trigger) to pause/resume or change the event, and [Delete event trigger](/api-reference/event-triggers/delete-event-trigger) to unregister it.
## One trigger per agent
An agent has **at most one** event trigger. Because of this:
* List responses contain zero or one item and `has_more` is always `false`.
* Attempting to [create](/api-reference/event-triggers/create-event-trigger) a second trigger for an agent that already has one is rejected with `409 Conflict`. To change the trigger, update it in place or delete it and create a new one.
## Trigger event format
`trigger_event` uses the `"ResourceName:eventType"` format — the Procore resource name, a colon, then the event type. Examples:
* `"Tasks:create"`
* `"Project Users:update"`
## Procore prerequisites
Event triggers register an underlying provider webhook subscription. For Procore, this depends on configuration that lives **outside** this API:
* The teamspace must have a Procore **company/project mapping** configured.
* The teamspace must have **connected Procore credentials**.
These are set up out of band in teamspace settings. If they are missing or invalid when you create or update a trigger, the trigger is still saved, but the webhook cannot be fully wired up — the gap is reported through the `warnings` array rather than failing the request.
## Warnings
Create and update responses include a `warnings` array. Warnings describe **non-fatal** prerequisite gaps encountered while wiring up the provider webhook — the trigger row is persisted regardless, so the request does not fail. An empty array means the trigger was configured with no issues.
Each warning pairs a stable machine-readable `code` with a human-readable `message`. Branch on `code` rather than parsing `message`, which may change:
```json theme={null}
{
"warnings": [
{
"code": "procore_not_connected",
"message": "Procore is not connected. Connect Procore in teamspace settings and try again"
}
]
}
```
Known codes include `procore_not_connected`, `procore_not_configured`, `trigger_event_update_failed`, `procore_connection_expired`, `procore_scope_incorrect`, and `procore_registration_rejected`. Any unrecognized warning falls back to `registration_failed`. Treat the code set as open-ended and default to surfacing the `message` for codes you do not recognize.
## Related docs
* [Create event trigger](/api-reference/event-triggers/create-event-trigger)
* [List event triggers](/api-reference/event-triggers/list-event-triggers)
* [Update event trigger](/api-reference/event-triggers/update-event-trigger)
* [Delete event trigger](/api-reference/event-triggers/delete-event-trigger)
# Update event trigger
Source: https://developers.datagrid.com/api-reference/event-triggers/update-event-trigger
patch /agents/{agent_id}/event-triggers/{trigger_id}
Update an existing event trigger on the specified agent. Only the fields supplied in the request body are changed; omitted fields keep their current value. Use `enabled` to pause (`false`) or resume (`true`) the trigger. As with creation, non-fatal prerequisite gaps are returned in the `warnings` array rather than failing the request.
# Create files
Source: https://developers.datagrid.com/api-reference/files/create-files
post /files
Create files which can be passed as input to agents. This endpoint consumes a flat credit charge per upload. The response includes a `credits` field with the amount consumed, or `null` if the billing write fails — the upload still succeeds in that case.
# Delete file
Source: https://developers.datagrid.com/api-reference/files/delete-file
delete /files/{file_id}
Delete file.
# List files
Source: https://developers.datagrid.com/api-reference/files/list-files
get /files
Returns the list of files.
# Retrieve file
Source: https://developers.datagrid.com/api-reference/files/retrieve-file
get /files/{file_id}
Retrieves a file by id.
# Retrieve file content
Source: https://developers.datagrid.com/api-reference/files/retrieve-file-content
get /files/{file_id}/content
Returns the content of a file.
# Update file
Source: https://developers.datagrid.com/api-reference/files/update-file
patch /files/{file_id}
Update file metadata.
# Retrieve current identity
Source: https://developers.datagrid.com/api-reference/identity/retrieve-current-identity
get /identity
Returns the identity of the authenticated caller — the user ID, current teamspace, and all teamspace memberships that the API key or JWT resolves to.
# Create knowledge
Source: https://developers.datagrid.com/api-reference/knowledge/create-knowledge
post /knowledge
Create knowledge which will be learned and leveraged by agents. Processing continues asynchronously after the request returns. If the background processing run later fails, subsequent retrievals surface that terminal state through `status` and `last_error`.
## Upload size limits
Each file must be within your plan’s per-file size cap. If a file is too large, the API returns **413** (`payload_too_large`) with guidance and a link to [Knowledge upload limits](/knowledge/upload-limits).
# Create knowledge from connection
Source: https://developers.datagrid.com/api-reference/knowledge/create-knowledge-from-connection
post /knowledge/connect
Initiates knowledge creation from a connection by returning a redirect URL. The organization must have enough credits to start this flow. The downstream ingestion and indexing that follow still run asynchronously, and the actual credit consumption remains variable based on the volume of data processed.
# Delete knowledge
Source: https://developers.datagrid.com/api-reference/knowledge/delete-knowledge
delete /knowledge/{knowledge_id}
Delete knowledge.
# List knowledge
Source: https://developers.datagrid.com/api-reference/knowledge/list-knowledge
get /knowledge
Returns a list of knowledge.
# Reindex knowledge
Source: https://developers.datagrid.com/api-reference/knowledge/reindex-knowledge
post /knowledge/{knowledge_id}/reindex
Manually trigger a full re-indexing of the knowledge. The reindex runs **asynchronously**: the API returns as soon as the job is enqueued. Re-indexing is not performed immediately. This endpoint consumes credits — the actual credit cost is variable, based on the volume of data being re-indexed, and is charged asynchronously as processing completes. If the background re-index later fails, subsequent retrievals surface that terminal state through `status` and `last_error`.
# Retrieve knowledge
Source: https://developers.datagrid.com/api-reference/knowledge/retrieve-knowledge
get /knowledge/{knowledge_id}
Retrieves knowledge by id.
# Update knowledge
Source: https://developers.datagrid.com/api-reference/knowledge/update-knowledge
patch /knowledge/{knowledge_id}
Update a knowledge's attributes. Each request can include either `files` or `sync`, but not both, and `files` cannot be combined with `semantic_indexing_enabled: true` — upload files first, then enable indexing in a separate request. When `files` are provided, all existing data is replaced and a re-processing pipeline runs asynchronously — this consumes credits based on the volume of data processed. Setting `semantic_indexing_enabled` to `true` on SQL-only knowledge is a one-way upgrade: it enables semantic indexing, consumes credits, and triggers an asynchronous backfill of existing records. If the upgrade is persisted but the backfill fails to start, the request returns `503` — retry the backfill with `POST /v1/knowledge/{knowledge_id}/reindex` (repeating the PATCH will not re-trigger it, because indexing is already enabled). Metadata-only and sync-only updates do not consume credits and are not blocked by credit eligibility checks. If the asynchronous processing run later fails, subsequent retrievals surface that terminal state through `status` and `last_error`.
## Upload size limits
Files added in a multipart update must respect your plan’s per-file size limit. Oversized files receive **413** (`payload_too_large`); see [Knowledge upload limits](/knowledge/upload-limits) for typical caps and strategies.
# Complete MCP OAuth flow
Source: https://developers.datagrid.com/api-reference/mcp-servers/complete-mcp-oauth-flow
post /organization/mcp-servers/{server_id}/oauth-callback
Complete an OAuth authorization flow for a registered MCP server by exchanging the authorization code returned from the OAuth provider. Requires a user-scoped API key (`dg_live_`); service keys (`dg_svc_`) are rejected with `mcp_oauth_requires_user_principal`.
# Create MCP server
Source: https://developers.datagrid.com/api-reference/mcp-servers/create-mcp-server
post /organization/mcp-servers
Register a new MCP server in the current teamspace. Each teamspace can have at most 30 registered MCP servers.
Registers a new MCP server at teamspace scope.
Use this first in the MCP setup flow.
Datagrid automatically attempts an initial tool sync after create.
Each teamspace can register up to **30 MCP servers**. If the limit is reached, the request returns `409 Conflict`. Remove an existing server before adding a new one.
You can provide auth in either form:
* `authorization_secret_id`: reference an existing Datagrid secret
* `authorization`: raw Authorization header value (Datagrid stores it as a secret automatically)
If both are sent, `authorization` takes precedence.
Ownership note:
* `authorization` creates a Datagrid-managed secret for this MCP server. If you later rotate or clear that value, Datagrid cleans up the previously auto-created secret.
* `authorization_secret_id` references an existing secret that you manage. Datagrid uses the reference, but does not delete that secret automatically.
```bash theme={null}
curl -X POST https://api.datagrid.com/v1/organization/mcp-servers \
-H "Authorization: Bearer $DATAGRID_API_KEY" \
-H "Datagrid-Teamspace: $DATAGRID_TEAMSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "manhour",
"base_url": "https://mcp.example.com/mcp",
"transport": "http",
"authorization": "Bearer external-api-token"
}'
```
```python Python SDK theme={null}
server = client.organization.mcp_servers.create(
name="manhour",
base_url="https://mcp.example.com/mcp",
transport="http",
authorization="Bearer external-api-token",
)
```
```typescript TypeScript SDK theme={null}
const server = await client.organization.mcpServers.create({
name: "manhour",
base_url: "https://mcp.example.com/mcp",
transport: "http",
authorization: "Bearer external-api-token",
});
```
# Delete MCP server
Source: https://developers.datagrid.com/api-reference/mcp-servers/delete-mcp-server
delete /organization/mcp-servers/{server_id}
Delete a registered MCP server.
Deletes a registered MCP server from the teamspace.
Use this when decommissioning a server or replacing it with a new registration.
If this server is attached to agents, remove/replace those mappings as part of cleanup.
# List MCP servers
Source: https://developers.datagrid.com/api-reference/mcp-servers/list-mcp-servers
get /organization/mcp-servers
List registered MCP servers for the current teamspace.
Lists MCP servers registered in the current teamspace.
Use this to:
* find an existing `server_id` by name
* verify registration before agent attachment
# Overview
Source: https://developers.datagrid.com/api-reference/mcp-servers/overview
Register and manage MCP servers at teamspace scope for use in converse.
**Beta**: This feature is in beta. The API schema may change as we iterate on the design.
[MCP (Model Context Protocol)](https://modelcontextprotocol.io/) is an open standard that enables AI agents to interact with external tools and services.
Datagrid supports **registered MCP servers**: teamspace-level server registration with automatic tool sync and per-agent mapping.
## What This API Manages
* Teamspace-level MCP server registry
* Tool metadata sync from each MCP server
* Per-agent MCP server mappings
* Auth configuration for registered servers (`authorization` or `authorization_secret_id`) and optional per-agent overrides (`credential_id`)
After mapping a server to an agent, its MCP tools become available in `converse` for that agent.
## Secret Ownership
* Use `authorization` when you want Datagrid to persist the raw Authorization header for this MCP server. Datagrid stores it as a secret and treats that secret as MCP-server-managed.
* Use `authorization_secret_id` when you already have a Datagrid secret and want the MCP server to reference it. That secret remains caller-managed.
* When auth is rotated or cleared, Datagrid only auto-deletes secrets that it created from `authorization`.
## Provisioning Flow
1. [Create MCP server](/api-reference/mcp-servers/create-mcp-server)
2. Datagrid auto-syncs tools on create
3. Attach MCP servers to an agent via [Create agent](/api-reference/agents/create-agent) or [Update agent](/api-reference/agents/update-agent) using the `mcp_servers` field
4. Call `converse` with `agent_id`
## End-to-End Example
```python Python theme={null}
import os
from datagrid_ai import Datagrid
client = Datagrid(api_key=os.environ.get("DATAGRID_API_KEY"))
# 1) Register server
server = client.organization.mcp_servers.create(
name="project-api",
base_url="https://mcp.mycompany.com/mcp"
)
# 2) Create agent with MCP servers attached inline
agent = client.agents.create(
name="My Agent",
mcp_servers=[{"server_id": server.id}]
)
# 3) Run conversation
response = client.converse(
prompt="Use project tools and summarize next actions",
agent_id=agent.id
)
print(response.content[0].text)
```
```javascript JavaScript theme={null}
import Datagrid from 'datagrid-ai';
const client = new Datagrid({
apiKey: process.env['DATAGRID_API_KEY']
});
// 1) Register server
const server = await client.organization.mcpServers.create({
name: 'project-api',
base_url: 'https://mcp.mycompany.com/mcp'
});
// 2) Create agent with MCP servers attached inline
const agent = await client.agents.create({
name: 'My Agent',
mcp_servers: [{ server_id: server.id }]
});
// 3) Run conversation
const response = await client.converse({
prompt: 'Use project tools and summarize next actions',
agent_id: agent.id
});
console.log(response.content[0].text);
```
```bash curl theme={null}
# 1) Register server
curl -X POST https://api.datagrid.com/v1/organization/mcp-servers \
-H "Authorization: Bearer $DATAGRID_API_KEY" \
-H "Datagrid-Teamspace: $DATAGRID_TEAMSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "project-api",
"base_url": "https://mcp.mycompany.com/mcp"
}'
# 2) Create agent with MCP servers attached inline
curl -X POST https://api.datagrid.com/v1/agents \
-H "Authorization: Bearer $DATAGRID_API_KEY" \
-H "Datagrid-Teamspace: $DATAGRID_TEAMSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "My Agent",
"mcp_servers": [
{ "server_id": "SERVER_ID" }
]
}'
# 3) Run conversation
curl -X POST https://api.datagrid.com/v1/converse \
-H "Authorization: Bearer $DATAGRID_API_KEY" \
-H "Datagrid-Teamspace: $DATAGRID_TEAMSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "AGENT_ID",
"prompt": "Use project tools and summarize next actions"
}'
```
## Per-Request Credentials
Use `config.mcp_credentials` in the converse request to pass per-request credentials for registered MCP servers. This is useful when different users have their own tokens for the same server.
Keys are registered MCP server IDs. Provide either `authorization` (a raw Authorization header value) or `credential_id` (a reference to a stored credential). If both are present, `authorization` takes precedence.
```python Python theme={null}
response = client.converse(
prompt="Get my project status",
agent_id="AGENT_ID",
config={
"mcp_credentials": {
"server-id-1": {
"authorization": "Bearer user-specific-token"
},
"server-id-2": {
"credential_id": "stored-credential-id"
}
}
}
)
```
```javascript JavaScript theme={null}
const response = await client.converse({
prompt: 'Get my project status',
agent_id: 'AGENT_ID',
config: {
mcp_credentials: {
'server-id-1': {
authorization: 'Bearer user-specific-token'
},
'server-id-2': {
credential_id: 'stored-credential-id'
}
}
}
});
```
```bash curl theme={null}
curl -X POST https://api.datagrid.com/v1/converse \
-H "Authorization: Bearer $DATAGRID_API_KEY" \
-H "Datagrid-Teamspace: $DATAGRID_TEAMSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "AGENT_ID",
"prompt": "Get my project status",
"config": {
"mcp_credentials": {
"server-id-1": {
"authorization": "Bearer user-specific-token"
},
"server-id-2": {
"credential_id": "stored-credential-id"
}
}
}
}'
```
## Authentication Priority
When resolving auth for an MCP server at converse time, Datagrid uses the first available source:
1. **Per-request `authorization`** (highest)
2. **Per-request `credential_id`**
3. Agent mapping `credential_id`
4. Server `authorization_secret_id`
5. Server OAuth token
6. No auth
## Runtime Lifecycle
When MCP tools are available for a turn, Datagrid handles MCP lifecycle operations automatically:
1. **Initialization**: `initialize` then `notifications/initialized`
2. **Tool discovery**: `tools/list`
3. **Tool execution**: `tools/call`
4. **Session recovery**: automatic re-initialize/retry when sessions expire
All requests after initialization include `MCP-Session-Id` and `MCP-Protocol-Version`.
## Limits
* Each teamspace can register a maximum of **30 MCP servers**. Attempting to create a server beyond this limit returns a `409 Conflict` error. Remove an existing server before adding a new one.
## Security
* MCP server URLs must be HTTPS
* SSRF protection: private IPs, localhost, and internal domains are blocked
* Teamspace isolation is enforced for server and credential references
* Authorization values are isolated per server and never shared across servers
* Per-request credentials are not persisted; scoped to the individual request
## Local Development
For local MCP development, expose your local server through an HTTPS tunnel (e.g. ngrok or cloudflared), then register that URL as the `base_url`.
## Endpoints
**MCP Servers**
* [Create MCP server](/api-reference/mcp-servers/create-mcp-server)
* [Retrieve MCP server](/api-reference/mcp-servers/retrieve-mcp-server)
* [List MCP servers](/api-reference/mcp-servers/list-mcp-servers)
* [Update MCP server](/api-reference/mcp-servers/update-mcp-server)
* [Delete MCP server](/api-reference/mcp-servers/delete-mcp-server)
**Agent MCP mappings**
* Use `mcp_servers` in [Create agent](/api-reference/agents/create-agent) or [Update agent](/api-reference/agents/update-agent)
# Retrieve MCP server
Source: https://developers.datagrid.com/api-reference/mcp-servers/retrieve-mcp-server
get /organization/mcp-servers/{server_id}
Retrieve a registered MCP server.
Returns one registered MCP server configuration and metadata.
Use this to inspect:
* current `base_url`
* status and sync metadata
* configured `authorization_secret_id`
# Start MCP OAuth flow
Source: https://developers.datagrid.com/api-reference/mcp-servers/start-mcp-oauth-flow
post /organization/mcp-servers/{server_id}/start-oauth
Start an OAuth authorization flow for a registered MCP server and return the URL to redirect the end user to. Requires a user-scoped API key (`dg_live_`); service keys (`dg_svc_`) are rejected with `mcp_oauth_requires_user_principal`.
# Update MCP server
Source: https://developers.datagrid.com/api-reference/mcp-servers/update-mcp-server
patch /organization/mcp-servers/{server_id}
Update a registered MCP server.
Updates a registered MCP server.
Common uses:
* rotate endpoint URL (`base_url`)
* change display name
* set or clear server-level authorization (`authorization` or `authorization_secret_id`)
Datagrid automatically attempts a tool re-sync when MCP connectivity/auth fields are updated.
If both `authorization` and `authorization_secret_id` are sent, `authorization` takes precedence.
Ownership note:
* `authorization` creates or rotates a Datagrid-managed secret for this MCP server.
* `authorization_secret_id` switches the server to an existing caller-managed secret reference.
* Clearing auth removes the MCP server's reference. Datagrid only auto-deletes secrets that it created from `authorization`.
```bash theme={null}
curl -X PATCH https://api.datagrid.com/v1/organization/mcp-servers/SERVER_ID \
-H "Authorization: Bearer $DATAGRID_API_KEY" \
-H "Datagrid-Teamspace: $DATAGRID_TEAMSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"authorization": "Bearer rotated-token"
}'
```
```python Python SDK theme={null}
server = client.organization.mcp_servers.update(
"SERVER_ID",
authorization="Bearer rotated-token",
)
```
```typescript TypeScript SDK theme={null}
const server = await client.organization.mcpServers.update("SERVER_ID", {
authorization: "Bearer rotated-token",
});
```
# Create User Memory
Source: https://developers.datagrid.com/api-reference/memory/create-user-memory
post /user-memories
Create a user memory. This endpoint requires credits and meters the actual embedding work performed for the request. The response includes `credits.consumed` with the billed amount, or `null` if the billing write fails after the memory is successfully created.
# Delete User Memory
Source: https://developers.datagrid.com/api-reference/memory/delete-user-memory
delete /user-memories/{user_memory_id}
Delete a user memory
# List User Memory
Source: https://developers.datagrid.com/api-reference/memory/list-user-memory
get /user-memories
List the memories for a given user and agent that the user has access to
# Create page
Source: https://developers.datagrid.com/api-reference/pages/create-page
post /pages
Create a new page
# Delete page
Source: https://developers.datagrid.com/api-reference/pages/delete-page
delete /pages/{page_id}
Delete a page. The page must have no children or all its children must have been deleted before invoking this API.
# List pages
Source: https://developers.datagrid.com/api-reference/pages/list-pages
get /pages
List all pages for the authenticated organization
# Retrieve page
Source: https://developers.datagrid.com/api-reference/pages/retrieve-page
get /pages/{page_id}
Get details of a specific page
# Update page
Source: https://developers.datagrid.com/api-reference/pages/update-page
patch /pages/{page_id}
Update a page's attributes
# Rate Limits
Source: https://developers.datagrid.com/api-reference/rate-limits
Understand Datagrid API rate limits, response headers, and best practices for handling 429 responses
The Datagrid API enforces rate limits to protect the platform from abuse and ensure fair usage across all consumers.
## Default rate limit
The default rate limit is **200 requests per 60-second sliding window**, but individual endpoints may enforce their own limits. Check the `X-RateLimit-Limit` response header to see the effective limit for any given endpoint.
The limit is scoped per **teamspace**, **endpoint path**, and **HTTP method**. For example, `POST /v1/converse` and `GET /v1/agents` maintain independent rate limit windows within the same teamspace.
## Response headers
Every Datagrid API response includes rate limit headers so you can monitor your usage proactively:
| Header | Description | Example |
| ----------------------- | ----------------------------------------------------------------------------------------------------------- | ------------ |
| `X-RateLimit-Limit` | Maximum requests allowed in the current window | `200` |
| `X-RateLimit-Remaining` | Requests remaining in the current window | `195` |
| `X-RateLimit-Reset` | Unix epoch second when the current window resets | `1741275120` |
| `Retry-After` | Seconds until the window resets (present when `X-RateLimit-Remaining` is `0`, including on `429` responses) | `30` |
## 429 Too Many Requests
When the rate limit is exceeded, the API returns a `429` status code with the following body:
```json theme={null}
{
"status_code": 429,
"statusCode": 429,
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded",
"mitigation": "Implement exponential backoff and retry with delays",
"retryable": true,
"details": {
"reason": "Rate limit exceeded. Please retry after the current window resets."
}
}
```
`statusCode` is deprecated and will be removed in a future version. Use `status_code` instead.
The response also includes the `Retry-After` header indicating how many seconds to wait before retrying.
## Batch prediction rate limits
Batch prediction endpoints also use `429 Too Many Requests`, but they return RFC 9457 problem details (`application/problem+json`) instead of the legacy JSON shape above.
```json theme={null}
{
"type": "https://api.datagrid.com/errors/rate_limit_exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Too many batch prediction requests are already in progress for this teamspace."
}
```
Batch prediction `429` responses may also include:
| Header | Description |
| ----------------------- | ---------------------------------------------------------------------- |
| `X-RateLimit-Limit` | The numeric limit for the rule that rejected the request. |
| `X-RateLimit-Remaining` | Remaining capacity for the matched rule, usually `0` on `429`. |
| `X-RateLimit-Reset` | Unix epoch second when capacity is expected to reset, when applicable. |
| `RateLimit-Policy` | The batch admission-control rule that rejected the request. |
| `RateLimit` | Current limit state for the matched policy. |
| `Retry-After` | Seconds to wait before retrying. |
Batch create requests can be rejected by organization concurrency, enqueued-item, create-rate, or temporary global-capacity policies. Treat these responses as retryable and honor `Retry-After` before submitting more batch work.
## Best practices
The official [Python](https://github.com/DatagridAI/datagrid-python) and [TypeScript/JavaScript](https://github.com/DatagridAI/datagrid-node) SDKs automatically retry `429` responses up to 2 times with exponential backoff. You can configure this via the `maxRetries` option:
```python Python theme={null}
from datagrid_ai import Datagrid
client = Datagrid(max_retries=5) # default is 2; set to 0 to disable
```
```typescript TypeScript theme={null}
import Datagrid from 'datagrid-ai';
const client = new Datagrid({ maxRetries: 5 }); // default is 2; set to 0 to disable
```
If you are using the SDK, you typically do not need to implement your own retry logic.
If you are calling the API directly (without the SDK), implement retry logic yourself. When you receive a `429`, wait for the number of seconds specified in the `Retry-After` header before retrying. If `Retry-After` is not available, use exponential backoff starting at 1 second, doubling with each retry up to a maximum of 60 seconds.
```python Python theme={null}
import time
import httpx
def request_with_backoff(client, **kwargs):
max_retries = 5
delay = 1
for attempt in range(max_retries):
response = client.post(**kwargs)
if response.status_code != 429:
return response
retry_after = response.headers.get("Retry-After")
wait = int(retry_after) if retry_after else delay
time.sleep(wait)
delay = min(delay * 2, 60)
return response
```
```typescript TypeScript theme={null}
async function requestWithBackoff(
fn: () => Promise,
maxRetries = 5
): Promise {
let delay = 1000;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fn();
if (response.status !== 429) return response;
const retryAfter = response.headers.get("Retry-After");
const wait = retryAfter ? parseInt(retryAfter, 10) * 1000 : delay;
await new Promise((resolve) => setTimeout(resolve, wait));
delay = Math.min(delay * 2, 60_000);
}
return fn();
}
```
Check the `X-RateLimit-Remaining` header on every response. If it drops below a threshold (e.g., 10% of the limit), slow down your request rate before hitting a `429`.
Since rate limits are scoped per endpoint path and HTTP method, you can make concurrent requests to different endpoints without them counting against the same window.
# AI Search
Source: https://developers.datagrid.com/api-reference/search/ai-search
post /search/ai
AI-powered search that retrieves relevant knowledge from your teamspace, builds a merged context,
and generates a natural language answer with numbered source citations.
The response includes the generated answer, the sources cited (with links and metadata),
and the full search tree results for reference.
Use this when you want an AI-generated summary answer grounded in your team's data.
# Search
Source: https://developers.datagrid.com/api-reference/search/search
get /search
[DEPRECATED] Search across knowledge. Use /search/ai for AI-powered generative search or /search/tree for merged context tree results.
# Search Tree
Source: https://developers.datagrid.com/api-reference/search/search-tree
get /search/tree
Search across your teamspace's indexed knowledge and return results as a hierarchical context tree.
Results are grouped by source (datasets, files, pages) with navigation items for quick access.
Supports pagination via cursor-based `next` parameter.
This endpoint is the foundation for the AI Search endpoint — use this when you need structured results without AI summarization.
# Create secret
Source: https://developers.datagrid.com/api-reference/secrets/create-secret
post /secrets
Create a new secret that can be referenced in converse API calls.
# Delete secret
Source: https://developers.datagrid.com/api-reference/secrets/delete-secret
delete /secrets/{secret_id}
Delete a secret.
# List secrets
Source: https://developers.datagrid.com/api-reference/secrets/list-secrets
get /secrets
Returns the list of user-created secrets.
# Retrieve secret
Source: https://developers.datagrid.com/api-reference/secrets/retrieve-secret
get /secrets/{secret_id}
Retrieve a specific secret by ID.
# List records
Source: https://developers.datagrid.com/api-reference/tables/list-records
get /tables/{table_id}/records
Returns a list of records for a table.
# List summaries
Source: https://developers.datagrid.com/api-reference/tables/list-summaries
get /tables/{table_id}/summaries
Returns a list of AI-generated summary records for a knowledge table. Use `record_type=row` (the default) for per-row summaries, or `record_type=table` for the single table-level summary. This endpoint reflects the current index state and does not consume AI credits.
# List tables
Source: https://developers.datagrid.com/api-reference/tables/list-tables
get /tables
Returns a list of tables.
# Replicating Tables
Source: https://developers.datagrid.com/api-reference/tables/replicating-tables
Incrementally replicate table records to your destination system
Datagrid tables provide a cursor-based pagination system that enables efficient incremental replication of records. This is useful when you want to sync table data to an external system while only fetching new or updated records since your last replication.
## How It Works
The `tables.records.list` endpoint returns a `cursor` with each response. By storing this cursor and passing it as the `next` parameter in subsequent requests, you can fetch only the records that have changed since your last replication.
## Incremental Replication
The following example demonstrates how to implement incremental replication:
```javascript JavaScript theme={null}
import Datagrid from "datagrid-ai";
const datagrid = new Datagrid();
// Fetch the last sync cursor from your storage
const cursor = await fetchLastSyncCursor(tableId);
const response = await datagrid.knowledge.tables.records.list(tableId, {
next: cursor,
limit: 500,
});
for await (const page of response.iterPages()) {
// Push record batch to your destination system
await pushRecords(page.data);
// Store the cursor, indicating that you have replicated the data up to this point
await storeCursor(tableId, page.cursor);
}
```
```python Python theme={null}
from datagrid_ai import Datagrid
datagrid = Datagrid()
# Fetch the last sync cursor from your storage
cursor = await fetch_last_sync_cursor(table_id)
response = datagrid.knowledge.tables.records.list(
table_id,
next=cursor,
limit=500,
)
for page in response.iter_pages():
# Push record batch to your destination system
await push_records(page.data)
# Store the cursor, indicating that you have replicated the data up to this point
await store_cursor(table_id, page.cursor)
```
## Cursor Persistence
Always store the cursor **after** successfully processing each batch of records. This ensures that if your replication process fails mid-way, you can resume from the last successfully processed batch.
# Retrieve table
Source: https://developers.datagrid.com/api-reference/tables/retrieve-table
get /tables/{table_id}
Retrieves a table by id.
# Delete teamspace invite
Source: https://developers.datagrid.com/api-reference/teamspace-invites/delete-teamspace-invite
delete /organization/teamspaces/{teamspace_id}/invites/{invite_id}
Delete a pending invite for a user in a teamspace.
# Invite user to a teamspace
Source: https://developers.datagrid.com/api-reference/teamspace-invites/invite-user-to-a-teamspace
post /organization/teamspaces/{teamspace_id}/invites
Invite a user to join the teamspace. This will send an invitation email. If the user already exists, the invite will be automatically accepted.
# List teamspace invites
Source: https://developers.datagrid.com/api-reference/teamspace-invites/list-teamspace-invites
get /organization/teamspaces/{teamspace_id}/invites
List all pending invites for a teamspace.
# Retrieve teamspace invite
Source: https://developers.datagrid.com/api-reference/teamspace-invites/retrieve-teamspace-invite
get /organization/teamspaces/{teamspace_id}/invites/{invite_id}
Get a pending invite for in a teamspace.
# Delete teamspace user
Source: https://developers.datagrid.com/api-reference/teamspace-users/delete-teamspace-user
delete /organization/teamspaces/{teamspace_id}/users/{user_id}
Revoke a user's permissions from the teamspace.
# List teamspace users
Source: https://developers.datagrid.com/api-reference/teamspace-users/list-teamspace-users
get /organization/teamspaces/{teamspace_id}/users
Retrieve a list of users in the specified teamspace.
# Retrieve teamspace user
Source: https://developers.datagrid.com/api-reference/teamspace-users/retrieve-teamspace-user
get /organization/teamspaces/{teamspace_id}/users/{user_id}
Retrieve details of a specific user in the teamspace.
# Update teamspace user
Source: https://developers.datagrid.com/api-reference/teamspace-users/update-teamspace-user
patch /organization/teamspaces/{teamspace_id}/users/{user_id}
Update user permissions in the teamspace.
# Create teamspace
Source: https://developers.datagrid.com/api-reference/teamspaces/create-teamspace
post /organization/teamspaces
Create a new teamspace within your organization.
# List teamspaces
Source: https://developers.datagrid.com/api-reference/teamspaces/list-teamspaces
get /organization/teamspaces
Returns the list of teamspaces within your organization.
# Retrieve teamspace
Source: https://developers.datagrid.com/api-reference/teamspaces/retrieve-teamspace
get /organization/teamspaces/{teamspace_id}
Retrieve a specific teamspace by ID.
# Scope Requests to a Teamspace
Source: https://developers.datagrid.com/api-reference/teamspaces/scope-to-teamspace
Learn how to scope API requests to a different teamspace within your account
Teamspaces isolate resources and data within your organization. By default, API requests are scoped to the teamspace in which the API key was created — its **home teamspace**.
## When you can target a different teamspace
The `Datagrid-Teamspace` header only takes effect for API keys created with **`scopeLevel: "account"`**. For these account-scoped keys, the header may select any teamspace under the **same account** as the key's home teamspace.
For the default **`scopeLevel: "org"`** keys, the header is ignored — the request always runs against the home teamspace. To target a different teamspace with an org-scoped key, mint a new key inside the target teamspace.
| Key `scopeLevel` | `Datagrid-Teamspace` behavior |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `"org"` (default) | Header is ignored. Request runs against the key's home teamspace. |
| `"account"` | Header selects the target teamspace. Must be under the same account as the home teamspace; otherwise the request returns `404`. |
## Option 1: Initialize the client with a teamspace
Initialize the Datagrid client with a teamspace ID — all subsequent requests will use it automatically (account-scoped keys only).
```python Python theme={null}
from datagrid_ai import Datagrid
datagrid = Datagrid(
teamspace="teamspace_id",
)
response = datagrid.converse(
prompt="Hello world!",
)
print(response.content[0].text)
```
```javascript JavaScript theme={null}
import Datagrid from "datagrid-ai";
const datagrid = new Datagrid({
teamspace: "teamspace_id",
});
const response = await datagrid.converse({
prompt: "Hello world!",
});
console.log(response.content[0].text);
```
## Option 2: Scope individual requests
Pass the `Datagrid-Teamspace` header per request — useful when one client addresses multiple teamspaces in the same account.
```python Python theme={null}
from datagrid_ai import Datagrid
datagrid = Datagrid()
response = datagrid.converse(
prompt="Hello world!",
extra_headers={
"Datagrid-Teamspace": "teamspace_id",
},
)
print(response.content[0].text)
```
```javascript JavaScript theme={null}
import Datagrid from "datagrid-ai";
const datagrid = new Datagrid();
const response = await datagrid.converse(
{
prompt: "Hello world!",
},
{
headers: {
"Datagrid-Teamspace": "teamspace_id",
},
}
);
console.log(response.content[0].text);
```
# Update teamspace
Source: https://developers.datagrid.com/api-reference/teamspaces/update-teamspace
patch /organization/teamspaces/{teamspace_id}
Update the name and/or access settings of a teamspace.
# List tools
Source: https://developers.datagrid.com/api-reference/tools/list-tools
get /tools
Returns the list of available tools that can be used by agents.
# Retrieve tool
Source: https://developers.datagrid.com/api-reference/tools/retrieve-tool
get /tools/{tool_name}
Retrieves a specific tool by its identifier.
# List users
Source: https://developers.datagrid.com/api-reference/users/list-users
get /organization/users
Retrieve a list of users in the specified organization.
# Retrieve user
Source: https://developers.datagrid.com/api-reference/users/retrieve-user
get /organization/users/{user_id}
Retrieve details of a specific user in the organization.
# Update user
Source: https://developers.datagrid.com/api-reference/users/update-user
patch /organization/users/{user_id}
Update user permissions in the organization.
# Acknowledge voice orchestrator task
Source: https://developers.datagrid.com/api-reference/voice/acknowledge-voice-orchestrator-task
patch /voice-orchestrator/tasks/{task_id}/acknowledge
# Cancel voice orchestrator task
Source: https://developers.datagrid.com/api-reference/voice/cancel-voice-orchestrator-task
patch /voice-orchestrator/tasks/{task_id}/cancel
# iOS / Swift Integration
Source: https://developers.datagrid.com/api-reference/voice/ios-integration
Build a real-time voice assistant in an iOS app using the Datagrid Voice WebSocket API
This guide walks through building a voice assistant in a Swift iOS app. You'll capture microphone audio, stream it to the Datagrid Voice API over a WebSocket, and play back the agent's audio responses in real time.
This guide uses the **direct WebSocket** approach — connecting straight to `wss://api.datagrid.com/ws/voice`. You can also use the [REST endpoint](/api-reference/voice/voice#option-a-sdk--rest-recommended) (`POST /v1/voice`) to get a pre-built WebSocket URL and start message first.
## Overview
The integration has four parts:
1. **WebSocket connection** — Connect to the voice endpoint and manage the message protocol
2. **Audio capture** — Record microphone input as 16-bit mono PCM at 16kHz
3. **Audio playback** — Play the agent's response audio (16-bit mono PCM at 24kHz)
4. **UI** — A simple button to start/stop the conversation
## 1. WebSocket Client
Create a class that manages the WebSocket connection and message routing:
```swift theme={null}
import Foundation
protocol VoiceSessionDelegate: AnyObject {
func voiceSessionDidConnect(_ session: VoiceSession)
func voiceSession(_ session: VoiceSession, didReceiveAudio base64Audio: String)
func voiceSessionDidBecomeReady(_ session: VoiceSession)
func voiceSession(_ session: VoiceSession, didStartSession sessionId: String, conversationId: String)
func voiceSession(_ session: VoiceSession, didReceiveToolCall toolName: String, status: String)
func voiceSessionWasInterrupted(_ session: VoiceSession)
func voiceSession(_ session: VoiceSession, didEnd payload: [String: Any])
func voiceSession(_ session: VoiceSession, didReceiveError message: String)
}
class VoiceSession: NSObject, URLSessionWebSocketDelegate {
weak var delegate: VoiceSessionDelegate?
private var webSocket: URLSessionWebSocketTask?
private var urlSession: URLSession?
private let apiKey: String
private let baseURL: String
init(apiKey: String, baseURL: String = "wss://api.datagrid.com") {
self.apiKey = apiKey
self.baseURL = baseURL
super.init()
}
// MARK: - Connection
func connect() {
let urlString = "\(baseURL)/ws/voice?token=\(apiKey)"
guard let url = URL(string: urlString) else { return }
urlSession = URLSession(
configuration: .default,
delegate: self,
delegateQueue: .main
)
webSocket = urlSession?.webSocketTask(with: url)
webSocket?.resume()
listenForMessages()
}
func disconnect() {
webSocket?.cancel(with: .goingAway, reason: nil)
webSocket = nil
}
// MARK: - URLSessionWebSocketDelegate
func urlSession(
_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
didOpenWithProtocol protocol: String?
) {
delegate?.voiceSessionDidConnect(self)
}
// MARK: - Sending Messages
func startSession(agentId: String? = nil, conversationId: String? = nil) {
var payload: [String: Any] = [:]
if let agentId { payload["agent_id"] = agentId }
if let conversationId { payload["conversation_id"] = conversationId }
send(type: "start", payload: payload)
}
func sendAudio(base64PCM: String) {
send(type: "audio", payload: ["data": base64PCM])
}
func stop() {
send(type: "stop")
}
func interrupt() {
send(type: "interrupt")
}
// MARK: - Private
private func send(type: String, payload: [String: Any]? = nil) {
var message: [String: Any] = ["type": type]
if let payload { message["payload"] = payload }
guard let data = try? JSONSerialization.data(withJSONObject: message),
let string = String(data: data, encoding: .utf8) else { return }
webSocket?.send(.string(string)) { error in
if let error {
print("[VoiceSession] Send error: \(error.localizedDescription)")
}
}
}
private func listenForMessages() {
webSocket?.receive { [weak self] result in
guard let self else { return }
switch result {
case .success(let message):
switch message {
case .string(let text):
self.handleMessage(text)
case .data(let data):
if let text = String(data: data, encoding: .utf8) {
self.handleMessage(text)
}
@unknown default:
break
}
// Continue listening
self.listenForMessages()
case .failure(let error):
print("[VoiceSession] Receive error: \(error.localizedDescription)")
}
}
}
private func handleMessage(_ text: String) {
guard let data = text.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let type = json["type"] as? String else { return }
let payload = json["payload"] as? [String: Any]
switch type {
case "started":
let sessionId = payload?["session_id"] as? String ?? ""
let conversationId = payload?["conversation_id"] as? String ?? ""
delegate?.voiceSession(self, didStartSession: sessionId, conversationId: conversationId)
case "ready":
delegate?.voiceSessionDidBecomeReady(self)
case "audio":
if let audioData = payload?["data"] as? String {
delegate?.voiceSession(self, didReceiveAudio: audioData)
}
case "tool_call":
let toolName = payload?["tool_name"] as? String ?? ""
let status = payload?["status"] as? String ?? ""
delegate?.voiceSession(self, didReceiveToolCall: toolName, status: status)
case "interrupted":
delegate?.voiceSessionWasInterrupted(self)
case "error":
let message = payload?["message"] as? String ?? "Unknown error"
delegate?.voiceSession(self, didReceiveError: message)
case "ended":
delegate?.voiceSession(self, didEnd: payload ?? [:])
default:
print("[VoiceSession] Unknown message type: \(type)")
}
}
}
```
## 2. Audio Capture (Microphone)
Use `AVAudioEngine` to capture microphone audio and convert it to 16-bit PCM:
```swift theme={null}
import AVFoundation
class AudioCapture {
private let audioEngine = AVAudioEngine()
var onAudioCaptured: ((String) -> Void)? // base64 PCM callback
func start() throws {
let inputNode = audioEngine.inputNode
let recordingFormat = AVAudioFormat(
commonFormat: .pcmFormatInt16,
sampleRate: 16000,
channels: 1,
interleaved: true
)!
// Install a tap to capture audio buffers
inputNode.installTap(
onBus: 0,
bufferSize: 4096,
format: recordingFormat
) { [weak self] buffer, _ in
guard let self,
let channelData = buffer.int16ChannelData else { return }
let frameCount = Int(buffer.frameLength)
let data = Data(
bytes: channelData.pointee,
count: frameCount * MemoryLayout.size
)
let base64 = data.base64EncodedString()
self.onAudioCaptured?(base64)
}
try audioEngine.start()
}
func stop() {
audioEngine.inputNode.removeTap(onBus: 0)
audioEngine.stop()
}
}
```
## 3. Audio Playback
Use `AVAudioPlayerNode` to play back the agent's PCM audio in real time:
```swift theme={null}
import AVFoundation
class AudioPlayer {
private let audioEngine = AVAudioEngine()
private let playerNode = AVAudioPlayerNode()
private let playbackFormat: AVAudioFormat
init() {
// Agent audio is 16-bit mono PCM at 24kHz
playbackFormat = AVAudioFormat(
commonFormat: .pcmFormatInt16,
sampleRate: 24000,
channels: 1,
interleaved: true
)!
audioEngine.attach(playerNode)
audioEngine.connect(playerNode, to: audioEngine.mainMixerNode, format: playbackFormat)
try? audioEngine.start()
playerNode.play()
}
func enqueue(base64Audio: String) {
guard let audioData = Data(base64Encoded: base64Audio) else { return }
let frameCount = UInt32(audioData.count / MemoryLayout.size)
guard let buffer = AVAudioPCMBuffer(
pcmFormat: playbackFormat,
frameCapacity: frameCount
) else { return }
buffer.frameLength = frameCount
audioData.withUnsafeBytes { rawBuffer in
if let src = rawBuffer.baseAddress {
memcpy(buffer.int16ChannelData!.pointee, src, audioData.count)
}
}
playerNode.scheduleBuffer(buffer)
}
func stop() {
playerNode.stop()
audioEngine.stop()
}
}
```
## 4. Putting It All Together
Here's a SwiftUI view that ties everything together:
```swift theme={null}
import SwiftUI
import AVFoundation
struct VoiceAssistantView: View {
@StateObject private var viewModel = VoiceAssistantViewModel()
var body: some View {
VStack(spacing: 24) {
Text(viewModel.statusText)
.font(.headline)
.foregroundColor(.secondary)
Button(action: { viewModel.toggleVoice() }) {
Image(systemName: viewModel.isActive ? "mic.fill" : "mic")
.font(.system(size: 48))
.foregroundColor(viewModel.isActive ? .red : .blue)
}
.padding()
if !viewModel.transcript.isEmpty {
ScrollView {
LazyVStack(alignment: .leading, spacing: 8) {
ForEach(viewModel.transcript, id: \.self) { line in
Text(line)
.font(.body)
}
}
.padding()
}
}
}
.padding()
}
}
@MainActor
class VoiceAssistantViewModel: ObservableObject {
@Published var isActive = false
@Published var statusText = "Tap the mic to start"
@Published var transcript: [String] = []
private var voiceSession: VoiceSession?
private var audioCapture: AudioCapture?
private var audioPlayer: AudioPlayer?
func toggleVoice() {
if isActive {
stopSession()
} else {
startSession()
}
}
private func startSession() {
// 1. Configure audio session
let audioSession = AVAudioSession.sharedInstance()
try? audioSession.setCategory(.playAndRecord, mode: .voiceChat)
try? audioSession.setActive(true)
// 2. Create voice session
let apiKey = ProcessInfo.processInfo.environment["DATAGRID_API_KEY"] ?? ""
voiceSession = VoiceSession(apiKey: apiKey)
voiceSession?.delegate = self
voiceSession?.connect()
// 3. Create audio components
audioCapture = AudioCapture()
audioPlayer = AudioPlayer()
statusText = "Connecting..."
}
private func stopSession() {
voiceSession?.stop()
audioCapture?.stop()
audioPlayer?.stop()
isActive = false
statusText = "Tap the mic to start"
}
}
extension VoiceAssistantViewModel: VoiceSessionDelegate {
nonisolated func voiceSessionDidConnect(_ session: VoiceSession) {
Task { @MainActor in
statusText = "Connected"
// Send "start" immediately after the WebSocket opens.
session.startSession(agentId: "agent_abc123")
}
}
nonisolated func voiceSession(
_ session: VoiceSession,
didStartSession sessionId: String,
conversationId: String
) {
Task { @MainActor in
statusText = "Session started"
}
}
nonisolated func voiceSessionDidBecomeReady(_ session: VoiceSession) {
Task { @MainActor in
statusText = "Listening..."
isActive = true
// Start capturing microphone and streaming to server
audioCapture?.onAudioCaptured = { [weak session] base64PCM in
session?.sendAudio(base64PCM: base64PCM)
}
try? audioCapture?.start()
}
}
nonisolated func voiceSession(_ session: VoiceSession, didReceiveAudio base64Audio: String) {
Task { @MainActor in
statusText = "Agent speaking..."
audioPlayer?.enqueue(base64Audio: base64Audio)
}
}
nonisolated func voiceSession(
_ session: VoiceSession,
didReceiveToolCall toolName: String,
status: String
) {
Task { @MainActor in
if status == "started" {
statusText = "Using \(toolName)..."
}
}
}
nonisolated func voiceSessionWasInterrupted(_ session: VoiceSession) {
Task { @MainActor in
statusText = "Listening..."
}
}
nonisolated func voiceSession(_ session: VoiceSession, didEnd payload: [String: Any]) {
Task { @MainActor in
if let transcriptItems = payload["transcript"] as? [[String: String]] {
transcript = transcriptItems.map { item in
let role = item["role"] ?? "unknown"
let text = item["text"] ?? ""
return "\(role): \(text)"
}
}
stopSession()
}
}
nonisolated func voiceSession(_ session: VoiceSession, didReceiveError message: String) {
Task { @MainActor in
statusText = "Error: \(message)"
stopSession()
}
}
}
```
## Important Notes
### Audio Formats
* **Microphone input**: 16-bit mono PCM, 16kHz sample rate
* **Agent response**: 16-bit mono PCM, 24kHz sample rate
### Permissions
Add the following to your `Info.plist`:
```xml theme={null}
NSMicrophoneUsageDescriptionThis app needs microphone access for voice conversations.
```
### Interruption Handling
When the user starts speaking while the agent is responding, send an `interrupt` message to cut off the agent's response. You can detect this using Voice Activity Detection (VAD) or by monitoring microphone input levels.
### Error Handling & Reconnection
The WebSocket connection can drop due to network issues. In production, implement:
* Automatic reconnection with exponential backoff
* Graceful handling of `URLSessionWebSocketTask` delegate errors
* Audio session interruption handling (e.g., phone calls)
```swift theme={null}
// Example reconnection logic
func urlSession(
_ session: URLSession,
webSocketTask: URLSessionWebSocketTask,
didCloseWith closeCode: URLSessionWebSocketTask.CloseCode,
reason: Data?
) {
if closeCode != .goingAway {
// Unexpected disconnect — attempt reconnection
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.connect()
}
}
}
```
### Thread Safety
The `URLSessionWebSocketTask` delivers callbacks on the delegate queue. Make sure to dispatch UI updates and audio operations to the appropriate threads.
# List voice orchestrator tasks
Source: https://developers.datagrid.com/api-reference/voice/list-voice-orchestrator-tasks
get /voice-orchestrator/tasks
List delegated voice tasks for the authenticated user. By default this
returns active, non-expired queued or running tasks plus unacknowledged
terminal tasks and omits task result content from list views.
Pass an explicit status filter to request a specific task state.
A `cancelled` status marks tasks stopped via the cancel endpoint
(`PATCH /voice-orchestrator/tasks/{task_id}/cancel`).
# Retrieve voice orchestrator task
Source: https://developers.datagrid.com/api-reference/voice/retrieve-voice-orchestrator-task
get /voice-orchestrator/tasks/{task_id}
# Start voice session
Source: https://developers.datagrid.com/api-reference/voice/start-voice-session
post /voice
Prepare a real-time voice conversation with an AI Agent.
Returns a WebSocket URL and a ready-made `start` message. Open a WebSocket
connection to the returned `url`, send `start_message` as the first frame,
then stream audio back and forth.
This REST flow depends on Redis to issue a short-lived REST-to-WebSocket
handoff token. During a Redis incident, clients that can construct their
own `start` message may use the direct WebSocket flow below with a raw
API key.
You can also skip this endpoint and connect directly:
`wss://api.datagrid.com/ws/voice?token=YOUR_API_KEY`
**WebSocket Protocol:**
Once connected, send a JSON message with `type: "start"` and the session parameters as the payload.
The server responds with `type: "started"` containing the session and conversation IDs,
followed by `type: "ready"` when the agent is ready to receive audio.
**Audio Format:**
- Client → Server: 16-bit mono PCM at 16kHz, base64-encoded
- Server → Client: 16-bit mono PCM at 24kHz, base64-encoded
**Message Types:**
- Client: `start`, `audio`, `stop`, `interrupt`, `text`
- Server: `started`, `ready`, `audio`, `tool_call`, `interrupted`, `error`, `transcript`, `citation`, `ended`
# Voice Conversations
Source: https://developers.datagrid.com/api-reference/voice/voice
Real-time voice conversations with AI Agents using WebSockets
The Voice API enables real-time, bi-directional audio conversations with Datagrid AI Agents over WebSockets. Audio is streamed as base64-encoded PCM data, and the agent responds with synthesized speech in real time.
## How it works
1. **Start a session** — Call the REST endpoint or connect directly via WebSocket
2. **Connect** — Open a WebSocket connection to the returned URL
3. **Stream audio** — Send microphone audio as base64 PCM chunks; receive audio responses the same way
4. **End the session** — Send a `stop` message, or simply close the WebSocket
## Getting Started
There are two ways to start a voice session. Choose the one that fits your stack.
### Option A: SDK / REST (recommended)
Call `POST /v1/voice` to validate your request and receive a WebSocket URL with a ready-made `start` message. Then connect to the URL and send the message as the first frame.
`POST /v1/voice` depends on Redis to issue a short-lived REST-to-WebSocket handoff token. During a Redis incident, clients that can construct their own `start` message may use the direct WebSocket flow below with a raw API key.
```python Python theme={null}
from datagrid import Datagrid
import asyncio
import websockets
import json
import base64
client = Datagrid()
# 1. Prepare the session via REST
session = client.voice.start_session(agent_id="agent_abc123")
# 2. Connect to the WebSocket URL
async def voice_session():
async with websockets.connect(session.url) as ws:
# 3. Send the pre-built start message
await ws.send(json.dumps(session.start_message))
# 4. Wait for ready
while True:
msg = json.loads(await ws.recv())
print(f"← {msg['type']}")
if msg["type"] == "started":
print(f" Session: {msg['payload']['session_id']}")
if msg["type"] == "ready":
break
# 5. Stream audio
with open("recording.pcm", "rb") as f:
while chunk := f.read(4096):
await ws.send(json.dumps({
"type": "audio",
"payload": {"data": base64.b64encode(chunk).decode()}
}))
# 6. End session and get transcript
await ws.send(json.dumps({"type": "stop"}))
while True:
msg = json.loads(await ws.recv())
if msg["type"] == "audio":
audio_bytes = base64.b64decode(msg["payload"]["data"])
# Play or save audio_bytes...
elif msg["type"] == "ended":
print("Transcript:", msg["payload"]["transcript"])
break
asyncio.run(voice_session())
```
```javascript JavaScript theme={null}
import Datagrid from "datagrid-ai";
import WebSocket from "ws";
const client = new Datagrid();
// 1. Prepare the session via REST
const session = await client.voice.startSession({
agent_id: "agent_abc123",
});
// 2. Connect to the WebSocket URL
const ws = new WebSocket(session.url);
ws.on("open", () => {
// 3. Send the pre-built start message
ws.send(JSON.stringify(session.start_message));
});
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
switch (msg.type) {
case "started":
console.log("Session:", msg.payload.session_id);
break;
case "ready":
console.log("Ready — start sending audio");
// Send audio chunks here...
break;
case "audio":
// Decode and play: Buffer.from(msg.payload.data, "base64")
break;
case "transcript":
console.log(`[${msg.payload.role}] ${msg.payload.text}`);
break;
case "ended":
console.log("Transcript:", msg.payload.transcript);
ws.close();
break;
case "error":
console.error("Error:", msg.payload.message);
break;
}
});
```
```bash cURL + wscat theme={null}
# 1. Prepare the session
curl -X POST https://api.datagrid.com/v1/voice \
-H "Authorization: Bearer $DATAGRID_API_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_id": "agent_abc123"}'
# Response:
# {
# "object": "voice.session",
# "url": "wss://api.datagrid.com/ws/voice?token=dg_live_...",
# "agent_id": "agent_abc123",
# "start_message": {"type":"start","payload":{"agent_id":"agent_abc123"}}
# }
# 2. Connect and send the start_message
wscat -c "wss://api.datagrid.com/ws/voice?token=$DATAGRID_API_KEY"
> {"type":"start","payload":{"agent_id":"agent_abc123"}}
```
### Option B: Direct WebSocket
If you prefer to skip the REST call, connect directly to the WebSocket endpoint with your API key:
```
wss://api.datagrid.com/ws/voice?token=YOUR_API_KEY
```
Then send a `start` message manually as the first frame:
```python Python theme={null}
import asyncio
import websockets
import json
import base64
import os
API_KEY = os.environ["DATAGRID_API_KEY"]
async def voice_session():
uri = f"wss://api.datagrid.com/ws/voice?token={API_KEY}"
async with websockets.connect(uri) as ws:
# 1. Start a session
await ws.send(json.dumps({
"type": "start",
"payload": {
"agent_id": "agent_abc123"
}
}))
# 2. Wait for ready
while True:
msg = json.loads(await ws.recv())
print(f"← {msg['type']}")
if msg["type"] == "ready":
break
# 3. Stream audio and collect responses...
# (same as Option A from step 5 onward)
asyncio.run(voice_session())
```
```javascript JavaScript theme={null}
const API_KEY = process.env.DATAGRID_API_KEY;
const WebSocket = require("ws");
const ws = new WebSocket(
`wss://api.datagrid.com/ws/voice?token=${API_KEY}`
);
ws.on("open", () => {
ws.send(JSON.stringify({
type: "start",
payload: { agent_id: "agent_abc123" }
}));
});
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
// Handle messages (same as Option A)
});
```
You must send a `start` message within **30 seconds** of connecting. If the server doesn't receive one in time, it closes the connection with code `4000` (idle timeout).
## Client → Server Messages
All messages are JSON objects with a `type` field and an optional `payload`.
### `start` — Begin a voice session
```json theme={null}
{
"type": "start",
"payload": {
"agent_id": "agent_abc123",
"conversation_id": "conv_xyz789",
"config": {
"system_prompt": "You are a helpful travel assistant.",
"custom_prompt": "Always respond in a friendly, conversational tone."
},
"knowledge_ids": ["know_123"],
"page_ids": ["page_456"],
"file_ids": ["file_789"],
"secret_ids": ["secret_012"],
"user": {
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com"
},
"initial_context": "The user is looking at their latest sales report.",
"ephemeral": false,
"voice_config": {
"voice_preset": "sage",
"silence_commit_ms": 30000,
"segment_max_duration_ms": 180000,
"silence_discard_ratio": 0.9,
"input_transcription": true,
"output_transcription": true
}
}
}
```
| Field | Type | Description |
| ----------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_id` | string \| null | Agent to use. If omitted, the default agent is used. |
| `conversation_id` | string \| null | Continue an existing conversation. If omitted, a new one is created. |
| `config` | object \| null | Prompt overrides — `system_prompt` and/or `custom_prompt`. Voice sessions always use Gemini Live, so LLM model, planning prompt, and tool overrides are not applicable. |
| `knowledge_ids` | string\[] \| null | Knowledge sources to make available to the agent. |
| `page_ids` | string\[] \| null | Pages (and their knowledge) to make available. |
| `file_ids` | string\[] \| null | Files to attach to the conversation. |
| `secret_ids` | string\[] \| null | Secrets to include in the context. |
| `user` | object \| null | Override user info (`first_name`, `last_name`, `email`). |
| `initial_context` | string \| null | Context text the agent will briefly address before listening. |
| `ephemeral` | boolean | When `true`, messages are not saved to conversation history. Default: `false`. |
| `voice_config` | object \| null | Voice session configuration options. See [Voice Configuration](#voice-configuration) below. |
### `audio` — Send an audio chunk
```json theme={null}
{
"type": "audio",
"payload": {
"data": "",
"mime_type": "audio/pcm;rate=16000"
}
}
```
Audio should be sent as **16-bit mono PCM at 16kHz**, base64-encoded. Wait for the `ready` message before sending audio.
### `stop` — End the session
```json theme={null}
{ "type": "stop" }
```
Gracefully ends the session. The server responds with an `ended` message containing the session transcript and credits consumed.
`stop` is optional. Closing the WebSocket connection also gracefully ends the session and commits all buffered content server-side. The only difference is that with `stop`, you receive the `ended` response containing the final transcript and credit usage before the connection closes.
### `interrupt` — Interrupt the agent
```json theme={null}
{ "type": "interrupt" }
```
Send this when the user starts speaking while the agent is responding. The agent will stop its current response and the server sends an `interrupted` message.
## Voice Configuration
The `voice_config` option in the `start` message allows you to customize voice session behavior:
| Field | Type | Default | Description |
| ------------------------- | ------- | ------------------------- | ----------------------------------------------------------------------------- |
| `voice_preset` | string | Agent's configured preset | Voice preset to use. See [Available Presets](#available-voice-presets) below. |
| `silence_commit_ms` | number | 30000 | Duration of silence (ms) before auto-committing a segment. |
| `segment_max_duration_ms` | number | 180000 | Maximum segment duration (ms) before force-commit (3 minutes). |
| `silence_discard_ratio` | number | 0.9 | Discard a segment if this fraction (0–1) of its audio is silence. |
| `input_transcription` | boolean | true | Enable transcription of user input audio. |
| `output_transcription` | boolean | true | Enable transcription of agent output audio. |
### Available Voice Presets
| Preset | Description |
| --------- | ---------------------------------- |
| `spark` | Bright, higher pitch |
| `ember` | Upbeat, middle pitch |
| `sage` | Informative, lower pitch (default) |
| `nova` | Firm, middle pitch |
| `vale` | Excitable, lower-middle pitch |
| `drift` | Youthful, higher pitch |
| `crest` | Firm, lower-middle pitch |
| `orbit` | Breezy, middle pitch |
| `brook` | Easy-going, middle pitch |
| `gleam` | Bright, middle pitch |
| `dusk` | Breathy, lower pitch |
| `prism` | Clear, lower-middle pitch |
| `coast` | Easy-going, lower-middle pitch |
| `velvet` | Smooth, lower pitch |
| `silk` | Smooth, middle pitch |
| `crystal` | Clear, middle pitch |
| `ridge` | Gravelly, lower pitch |
| `atlas` | Informative, middle pitch |
| `bloom` | Upbeat, higher pitch |
| `whisper` | Soft, higher pitch |
| `steel` | Firm, lower-middle pitch |
| `steady` | Even, lower-middle pitch |
| `cedar` | Mature, middle pitch |
| `forge` | Forward, middle pitch |
| `haven` | Friendly, lower-middle pitch |
| `tide` | Casual, lower-middle pitch |
| `meadow` | Gentle, middle pitch |
| `rhythm` | Lively, lower pitch |
| `quill` | Articulate, middle pitch |
| `glow` | Warm, lower-middle pitch |
## Server → Client Messages
### `started` — Session established
```json theme={null}
{
"type": "started",
"payload": {
"session_id": "sess_abc123",
"conversation_id": "conv_xyz789",
"message_id": "msg_def456"
}
}
```
Sent immediately after a `start` message is processed. Contains the IDs for the session, conversation, and initial message.
### `ready` — Agent is ready to receive audio
```json theme={null}
{ "type": "ready" }
```
**Wait for this message before sending audio chunks.** The agent needs a moment to initialize after the session starts.
### `audio` — Agent audio response
```json theme={null}
{
"type": "audio",
"payload": {
"data": "",
"mime_type": "audio/pcm;rate=24000"
}
}
```
Response audio is **16-bit mono PCM at 24kHz**. Multiple `audio` messages are sent in sequence as the agent speaks.
### `tool_call` — Agent is using a tool
```json theme={null}
{
"type": "tool_call",
"payload": {
"tool_name": "search_knowledge",
"status": "started"
}
}
```
Status is either `"started"` or `"completed"`. Use this to show loading indicators while the agent searches knowledge or uses other tools.
### `transcript` — Real-time transcription
```json theme={null}
{
"type": "transcript",
"payload": {
"role": "user",
"text": "What were our Q4 sales?"
}
}
```
Sent in real-time as transcription becomes available. The `role` field is either `"user"` or `"agent"`. Use this to display a live transcript as the conversation progresses.
### `citation` — Source citation
```json theme={null}
{
"type": "citation",
"payload": {
"citations": [
{
"citation": "Q4 revenue was $2.3M.",
"sources": [
{
"type": "pdf_page",
"source_name": "Q4 Sales Report.pdf",
"source_uri": "datagrid:file:...",
"confirmations": ["Q4 revenue: $2.3M"],
"page_number": 4,
"data_lake_item_id": "dli_123"
}
]
}
],
"timestamp_ms": 12500,
"message_id": "msg_abc123"
}
}
```
Sent when the agent references a knowledge source. Each source is a **superset** of the SSE `/v1/converse` `CitationSource` schema: the core fields (`type`, `source_name`, optional `source_id`/`source_uri`, and `confirmations`) are identical, so external clients can treat voice and SSE citations the same way. Sources also include optional per-type enrichment fields (e.g. `page_number`/`data_lake_item_id`/`item_type` for `pdf_page`, `table_id`/`content`/`record_title`/`status`/`fields`/`item_type` for `record`, `thumbnail_url` for `web_search`, `query_view`/`task_explanation` for `sql_query_result`, `fact_data` for `action`) that clients may use or ignore. The `timestamp_ms` is relative to the session start, and `message_id` is the stable id of the agent turn that produced the citations — use it to correlate a citation to the turn it supports.
### `interrupted` — Agent was interrupted
```json theme={null}
{ "type": "interrupted" }
```
Confirms that the agent's response was interrupted after a client `interrupt` message.
### `error` — An error occurred
```json theme={null}
{
"type": "error",
"payload": {
"message": "Description of what went wrong"
}
}
```
Errors do not necessarily close the session. Transient errors are recoverable — only fatal errors are followed by a WebSocket close.
### `ended` — Session ended
```json theme={null}
{
"type": "ended",
"payload": {
"credits_consumed": 5,
"transcript": [
{ "role": "user", "text": "What were our Q4 sales?" },
{ "role": "agent", "text": "Based on your sales report, Q4 revenue was $2.3M..." }
]
}
}
```
Sent when the session ends (either from a `stop` message, server-side timeout, or error). Contains the final transcript and credit usage.
## Session Lifecycle
```
Client Server
│ │
│── POST /v1/voice ────────────────→ │ (Optional: get URL + start_message)
│← ─ { url, start_message } ────── │
│ │
│─── WebSocket Connect (url) ──────→ │
│ │
│─── start_message ────────────────→ │
│ │
│← ─ { type: "started", ... } ──── │ (session_id, conversation_id, message_id)
│← ─ { type: "ready" } ─────────── │
│ │
│─── { type: "audio", ... } ──────→ │ (stream mic audio)
│─── { type: "audio", ... } ──────→ │
│ ... │
│ │
│← ─ { type: "transcript", ... } ── │ (real-time transcription)
│← ─ { type: "audio", ... } ────── │ (agent speaks back)
│← ─ { type: "audio", ... } ────── │
│← ─ { type: "citation", ... } ──── │ (source citations)
│ │
│─── { type: "interrupt" } ───────→ │ (user interrupts)
│← ─ { type: "interrupted" } ───── │
│ │
│─── { type: "stop" } ───────────→ │ (or just close the WebSocket)
│← ─ { type: "ended", ... } ────── │ (transcript + credits)
│ │
│─── WebSocket Close ──────────────→ │
```
## WebSocket Close Codes
| Code | Meaning |
| ------ | ------------------------------------------------------------ |
| `1000` | Normal closure |
| `1001` | Server shutting down |
| `1008` | Authentication failed or invalid API key |
| `1011` | Internal server error during connection |
| `4000` | Idle timeout — no `start` message received within 30 seconds |
## Voice Orchestrator Tasks
Default orchestrator voice sessions can delegate longer-running work to
specialist agents. When delegated work continues after the voice-safe turn
budget, the server persists task status so clients can show a user-scoped task
inbox.
Use these REST endpoints to surface delegated task state:
| Endpoint | Purpose |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `GET /v1/voice-orchestrator/tasks` | List non-expired active tasks plus unacknowledged terminal tasks for the authenticated user. List responses omit result content. |
| `GET /v1/voice-orchestrator/tasks/{task_id}` | Retrieve one owned, non-expired task with result or error content when available. |
| `PATCH /v1/voice-orchestrator/tasks/{task_id}/acknowledge` | Mark an owned terminal task as seen so it is not repeatedly surfaced. |
Supported task states are `queued`, `running`, `completed`, `failed`, and
`cancelled`. The `cancelled` state is reserved for terminal records produced by
future cancellation flows; this API does not currently expose a cancel
operation. Missing, unowned, expired, non-terminal acknowledgement, and
feature-disabled tasks are returned as not found.
The current backend persists task status and terminal results. In-flight
specialist execution still runs in the existing voice server process, so a
server crash or redeploy during execution can leave a task `running` until its
`expires_at` time.
## Audio Format Reference
| Direction | Format | Sample Rate | Channels | Encoding |
| --------------- | ---------- | ----------- | -------- | -------- |
| Client → Server | PCM 16-bit | 16 kHz | Mono | Base64 |
| Server → Client | PCM 16-bit | 24 kHz | Mono | Base64 |
## Platform Guides
* [iOS / Swift Integration](/api-reference/voice/ios-integration) — Full walkthrough for building a voice assistant in a native iOS app
# Create webhook
Source: https://developers.datagrid.com/api-reference/webhooks/create-webhook
post /webhooks
Create an HTTPS webhook subscription for your teamspace. Datagrid returns the signing secret only in this response; store it securely and use it to verify future `Datagrid-Signature` headers.
# Delete webhook
Source: https://developers.datagrid.com/api-reference/webhooks/delete-webhook
delete /webhooks/{webhook_id}
Delete a webhook subscription.
# List active webhooks for event
Source: https://developers.datagrid.com/api-reference/webhooks/list-active-webhooks-for-event
get /webhooks/active
Returns enabled webhook subscriptions for a specific event type.
# List webhooks
Source: https://developers.datagrid.com/api-reference/webhooks/list-webhooks
get /webhooks
Returns a cursor-paginated list of webhook subscriptions.
# Webhooks overview
Source: https://developers.datagrid.com/api-reference/webhooks/overview
Subscribe to Datagrid events and receive signed HTTPS callbacks.
Webhooks let your application react when asynchronous Datagrid work finishes. Create a webhook with an HTTPS `url` and one or more event types, then verify each delivery with the signing secret returned at creation time.
## Supported events
| Event type | Sent when | `data` payload |
| -------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `knowledge.processing.completed` | Knowledge processing transitions to `ready`. | Knowledge processing summary with `knowledge_id`, `teamspace_id`, `scope`, `status`, and `row_counts`. |
| `batch_prediction.completed` | A batch prediction reaches `completed`. | The `batch_prediction` object. |
| `batch_prediction.failed` | A batch prediction reaches `failed`. | The `batch_prediction` object. |
| `batch_prediction.expired` | A batch prediction reaches `expired`. | The `batch_prediction` object. |
| `batch_prediction.cancelled` | A batch prediction reaches `cancelled`. | The `batch_prediction` object. |
For batch prediction events, treat the webhook as a terminal-state notification. Call [Retrieve batch prediction results](/api-reference/batch-predictions/retrieve-batch-prediction-results) when you need the full NDJSON result stream.
## Create a subscription
```bash theme={null}
curl --request POST "$DATAGRID_API_URL/v1/webhooks" \
--header "Authorization: Bearer $DATAGRID_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"url": "https://example.com/webhooks/datagrid",
"events": [
"batch_prediction.completed",
"batch_prediction.failed"
]
}'
```
The create response includes `secret` once. Store it securely; later retrieve and list responses do not include the secret.
## Delivery format
Datagrid sends a JSON envelope to your endpoint:
```json theme={null}
{
"id": "evt_7f3d8a2c",
"event_type": "batch_prediction.completed",
"timestamp": 1775847600,
"data": {
"object": "batch_prediction",
"id": "bpred_abc123",
"status": "completed",
"results_url": "/v1/batch-predictions/bpred_abc123/results"
}
}
```
Return a `2xx` response after persisting the event. Use `id` as an idempotency key because Datagrid may retry failed deliveries.
## Signature verification
Each delivery includes a `Datagrid-Signature` header:
```text theme={null}
t=1775847600,v1=
```
Compute the expected `v1` with HMAC-SHA256 over `${timestamp}.${raw_body}` using the webhook secret, then compare it with the header value using a constant-time comparison.
# Retrieve webhook
Source: https://developers.datagrid.com/api-reference/webhooks/retrieve-webhook
get /webhooks/{webhook_id}
Retrieve a specific webhook subscription by ID.
# Update webhook
Source: https://developers.datagrid.com/api-reference/webhooks/update-webhook
patch /webhooks/{webhook_id}
Update webhook configuration. You can modify the URL, subscribed events, and enabled status.
# June 2026
Source: https://developers.datagrid.com/changelog/2026-06
API and SDK changes released in June 2026.
Additions, changes, deprecations, and breaking changes to the public Datagrid REST API and the `datagrid-ai` SDK, released in June 2026.
## Conversations
* ChangedCitation sources now include `external_url`, so a citation can be linked back to its original document. ([reference](/api-reference/conversations/list-messages))
# July 2026
Source: https://developers.datagrid.com/changelog/2026-07
API and SDK changes released in July 2026.
Additions, changes, deprecations, and breaking changes to the public Datagrid REST API and the `datagrid-ai` SDK, released in July 2026.
## Agents
* Changed`openai.gpt-5.5` is now an accepted `llm_model` value when creating or updating an agent, and when overriding the model on `POST /v1/converse`. ([reference](/api-reference/agents/list-agents))
* ChangedThe default `agent_model` changed from `magpie-2.0` to `magpie-2.5` for agent create and update, and for `POST /v1/converse`. ([reference](/api-reference/agents/list-agents))
* *Migration:* Set `agent_model` explicitly to stay on `magpie-2.0`.
## Conversations
* BreakingMessage content can now be a structured JSON block. `MessageContentJson` was added to the message `content` union with a matching `json` discriminator value, on `POST /v1/converse` and both message-read endpoints. ([reference](/api-reference/conversations/list-messages))
* *Migration:* Clients that switch exhaustively on the message content type must handle `json`. Treat unrecognized content types as a non-fatal default rather than throwing.
# August 2026
Source: https://developers.datagrid.com/changelog/2026-08
API and SDK changes released in August 2026.
Additions, changes, deprecations, and breaking changes to the public Datagrid REST API and the `datagrid-ai` SDK, released in August 2026.
## Converse
* Changed`POST /v1/converse` now resolves selected organization-shared `knowledge_ids` owned by sibling teamspaces in the same account. ([reference](/api-reference/converse/converse))
## Voice
* AddedAdded `PATCH /v1/voice-orchestrator/tasks/{task_id}/cancel` — Cancel voice orchestrator task. ([reference](/api-reference/voice/cancel-voice-orchestrator-task))
* Changed`POST /v1/voice`: added the new optional request property `discard_if_empty`. ([reference](/api-reference/voice/start-voice-session))
## Agents
* ChangedThe default `llm_model` changed from `gemini-2.5-flash` to `gemini-3.1-flash-lite` for agent create and update, and for `POST /v1/converse`. ([reference](/api-reference/agents/list-agents))
* *Migration:* Set `llm_model` explicitly to stay on `gemini-2.5-flash`.
## Automations
* AddedAdded `GET /v1/agents/{agent_id}/automations` — List automations. ([reference](/api-reference/automations/list-automations))
* AddedAdded `POST /v1/agents/{agent_id}/automations` — Create automation. ([reference](/api-reference/automations/create-automation))
* AddedAdded `DELETE /v1/agents/{agent_id}/automations/{automation_id}` — Delete automation. ([reference](/api-reference/automations/delete-automation))
* AddedAdded `GET /v1/agents/{agent_id}/automations/{automation_id}` — Retrieve automation. ([reference](/api-reference/automations/retrieve-automation))
* AddedAdded `PATCH /v1/agents/{agent_id}/automations/{automation_id}` — Update automation. ([reference](/api-reference/automations/update-automation))
## Tables
* AddedAdded `GET /v1/tables/{table_id}/summaries` — List summaries. ([reference](/api-reference/tables/list-summaries))
* ChangedFor Procore datasets, record `created_at` is reset to the ingestion time on each upsert and does not preserve the original Procore creation time. ([reference](/api-reference/tables/list-records))
## Search
* Changed`GET /v1/search` now resolves and searches selected organization-shared `knowledge_ids` owned by sibling teamspaces in the same account. ([reference](/api-reference/search/search))
## Conversations
* Breaking`DELETE /v1/conversations/{conversation_id}` now rejects the root conversation of **any** channel — direct messages and standard channels as well as Chats — with `400 Bad Request`. Previously only the Chats root was protected, and deleting another channel's root left that channel unreachable. ([reference](/api-reference/conversations/delete-conversation))
* *Migration:* Delete the individual conversations inside a channel rather than the channel's root conversation.
* Changed`GET /v1/conversations`: added the new optional `query` request parameter `has_messages`. ([reference](/api-reference/conversations/list-conversations))
* ChangedCitation sources now include `fields` and `status`, exposing which record fields backed a citation and whether the source is still available. ([reference](/api-reference/conversations/list-messages))
* ChangedCitation sources now include `item_type`, identifying the kind of record a citation points at. ([reference](/api-reference/conversations/list-messages))
## Event Triggers
* AddedAdded `GET /v1/agents/{agent_id}/event-triggers` — List event triggers. ([reference](/api-reference/event-triggers/list-event-triggers))
* AddedAdded `POST /v1/agents/{agent_id}/event-triggers` — Create event trigger. ([reference](/api-reference/event-triggers/create-event-trigger))
* AddedAdded `DELETE /v1/agents/{agent_id}/event-triggers/{trigger_id}` — Delete event trigger. ([reference](/api-reference/event-triggers/delete-event-trigger))
* AddedAdded `PATCH /v1/agents/{agent_id}/event-triggers/{trigger_id}` — Update event trigger. ([reference](/api-reference/event-triggers/update-event-trigger))
## MCP Servers
* ChangedMCP server responses now include `managed_by_app`. Servers provisioned by an installed app are visible in list and retrieve responses but are read-only: `PATCH` and `DELETE` return `404` for them. ([reference](/api-reference/mcp-servers/list-mcp-servers))
# All Connectors
Source: https://developers.datagrid.com/connectors/all-connectors
Complete list of all available connectors in Datagrid AI
## Connector List
### Accubid Anywhere
**Connector ID:** `accubid_anywhere`
***
### Acumatica
**Connector ID:** `acumatica`
***
### Airtable
**Connector ID:** `airtable`
***
### Amazon S3
**Connector ID:** `amazon_s3`
***
### Autodesk Construction Cloud (ACC)
**Connector ID:** `bim360`
***
### Avoma
**Connector ID:** `avoma`
***
### Azure Blob Storage
**Connector ID:** `azure_blob_storage`
***
### Azure Data Lake Storage
**Connector ID:** `azure_lake_storage`
***
### Big Commerce
**Connector ID:** `bigcommerce`
***
### BigQuery
**Connector ID:** `bigquery`
***
### Box
**Connector ID:** `box`
***
### Bridgit
**Connector ID:** `bridgit`
***
### BuildingConnected
**Connector ID:** `building_connected`
***
### CMiC
**Connector ID:** `cmic`
***
### Databricks
**Connector ID:** `databricks`
***
### Databricks Volumes
**Connector ID:** `databricks_volumes`
***
### Delta Sharing
**Connector ID:** `delta_sharing`
***
### Demo
**Connector ID:** `demo`
***
### Documentum
**Connector ID:** `documentum`
***
### DocuSign
**Connector ID:** `docusign`
***
### Drift
**Connector ID:** `drift`
***
### Dropbox
**Connector ID:** `dropbox`
***
### Dynamics
**Connector ID:** `dynamics`
***
### Egnyte
**Connector ID:** `egnyte`
***
### Email
**Connector ID:** `smtp`
***
### Email Sync
**Connector ID:** `imap`
***
### Exchange Rate
**Connector ID:** `exchangerate`
***
### Facebook Ads
**Connector ID:** `facebook_ads`
***
### Federal Reserve Economic Data
**Connector ID:** `fred_connector`
***
### FieldWire
**Connector ID:** `fieldwire`
***
### Freshdesk
**Connector ID:** `freshdesk`
***
### Github
**Connector ID:** `github`
***
### Gmail
**Connector ID:** `gmail`
***
### Google Ads
**Connector ID:** `google_ads`
***
### Google Analytics
**Connector ID:** `google_analytics`
***
### Google Calendar
**Connector ID:** `google_calendar`
***
### Google Drive
**Connector ID:** `google_drive`
***
### Google Sheets
**Connector ID:** `google_sheets`
***
### Grain
**Connector ID:** `grain`
***
### Highwire
**Connector ID:** `highwire`
***
### Hilti ON!Track
**Connector ID:** `hilti_ontrack`
***
### HTTP Fetch
**Connector ID:** `http_fetch`
***
### Hubspot
**Connector ID:** `hubspot`
***
### Intercom
**Connector ID:** `intercom`
***
### JDBC Mock
**Connector ID:** `jdbc_mock`
***
### LinkedIn
**Connector ID:** `linkedin`
***
### LinkedIn Pages
**Connector ID:** `linkedin_pages`
***
### Microsoft Fabric
**Connector ID:** `microsoft_fabric`
***
### Microsoft Planner
**Connector ID:** `microsoft_planner`
***
### Microsoft SQL Server (MSSQL)
**Connector ID:** `mssql`
***
### Microsoft Teams
**Connector ID:** `microsoft_teams`
***
### Mixpanel
**Connector ID:** `mixpanel`
***
### MySQL
**Connector ID:** `mysql`
***
### Newforma Konekt
**Connector ID:** `bim_track`
***
### Notion
**Connector ID:** `notion`
***
### OneDrive
**Connector ID:** `one_drive`
***
### Oracle Aconex
**Connector ID:** `aconex`
***
### Oracle Netsuite
**Connector ID:** `oracle_netsuite`
***
### Oracle Textura
**Connector ID:** `textura`
***
### Outlook
**Connector ID:** `outlook`
***
### Outreach
**Connector ID:** `outreach`
***
### P6 Primavera OracleCloud
**Connector ID:** `primavera_oraclecloud`
***
### Pinterest
**Connector ID:** `pinterest`
***
### PlanGrid
**Connector ID:** `plangrid`
***
### PostgreSQL
**Connector ID:** `postgres`
***
### Primavera Data Service
**Connector ID:** `primavera_data_service`
***
### Primavera P6 EPPM
**Connector ID:** `primavera_p6_eppm`
***
### Procore
**Connector ID:** `procore`
***
### Procore in Session
**Connector ID:** `procore_in_session`
***
### Quickbase
**Connector ID:** `quickbase`
***
### QuickBooks
**Connector ID:** `quickbooks`
***
### Remarcable
**Connector ID:** `remarcable`
***
### Revizto
**Connector ID:** `revizto`
***
### Riskcast
**Connector ID:** `riskcast`
***
### Sage 300
**Connector ID:** `sage300`
***
### Sage Intacct
**Connector ID:** `sage_intacct`
***
### Salesforce
**Connector ID:** `salesforce`
***
### SAP S/4HANA
**Connector ID:** `sap_s4hana`
***
### Sentry
**Connector ID:** `sentry`
***
### SharePoint
**Connector ID:** `sharepoint`
***
### Sitesense
**Connector ID:** `sitesense`
***
### Slack
**Connector ID:** `slack`
***
### Smartsheet
**Connector ID:** `smartsheet`
***
### Stripe
**Connector ID:** `stripe`
***
### SurveyMonkey
**Connector ID:** `surveymonkey`
***
### Synchro 4D Pro
**Connector ID:** `synchro_4d_pro`
***
### TradeTapp
**Connector ID:** `tradetapp`
***
### Trimble Connect
**Connector ID:** `trimble_connect`
***
### Trimble ProjectSight
**Connector ID:** `trimble_project_sight`
***
### Viewpoint Vista
**Connector ID:** `viewpoint_vista`
***
### Web Scraper
**Connector ID:** `zyte`
***
### Webflow
**Connector ID:** `webflow`
***
### Wrike
**Connector ID:** `wrike`
***
### Yardi
**Connector ID:** `yardi`
***
### Zendesk - Sell
**Connector ID:** `zendesk_sell`
***
### Zendesk - Support
**Connector ID:** `zendesk_support`
***
## Summary
Total number of connectors: **96**
This list is automatically generated from the connector configuration. For the most up-to-date information about specific connectors, please refer to the individual connector documentation or contact support.
# Connecting Agents to Apps
Source: https://developers.datagrid.com/introduction/connections
Learn how to embed Datagrid's connection creation flow in your application
# Connection Creation
The Datagrid API provides an iframe-based embedding solution for connection creation that allows you to integrate third-party service authentication flows directly into your application.
## Overview
When you need to create connections to third-party services (like Google Drive, Hubspot, Dropbox, etc.), you can embed the Datagrid API connection creation flow in an iframe. The iframe will handle the OAuth flow and authentication, then communicate the results back to your parent application via postMessage events.
There are two main flows:
1. **Connection Creation** - Authenticate with a third-party service to create a connection
2. **Knowledge from Connection** - Use an existing connection to import data as knowledge for your agents
## API Flow
Before embedding the iframes, you need to call the appropriate API endpoints to get the redirect URLs.
### Step 1: Create a Connection
To create a connection, call the `createConnectionRedirectUrl` endpoint:
```typescript theme={null}
// Get the redirect URL for creating a new connection
const { redirect_url } = await datagridClient.connections.createConnectionRedirectUrl({
connector_id: "google_drive", // The connector type (google_drive, hubspot, dropbox, etc.)
});
// Use this redirect_url with the ConnectionEmbed component
```
### Step 2: Create Knowledge from Connection (Optional)
After a connection is created, you can create knowledge from it. Call the `createKnowledgeFromConnection` endpoint:
```typescript theme={null}
// Get the redirect URL for creating knowledge from an existing connection
const { redirect_url } = await datagridClient.knowledge.createKnowledgeFromConnection({
connection_id: "conn_abc123", // The ID of the connection created in Step 1
});
// Use this redirect_url with the KnowledgeFromConnectionEmbed component
```
## Connection Embed Implementation
The `ConnectionEmbed` component handles the connection creation flow. It receives the `redirect_url` from the `createConnectionRedirectUrl` API call.
Here's a React component example that demonstrates how to embed the connection creation iframe:
```tsx theme={null}
import React, { FC, useRef, useEffect, useState } from "react";
import { Typography, Box } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
const useStyles = makeStyles((theme) => ({
root: {
height: "100%",
width: "100%",
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
},
iframeWrapper: {
display: "flex",
flexDirection: "column",
gap: theme.spacing(4),
},
iframe: {
border: "none",
borderRadius: theme.spacing(1),
boxShadow: theme.shadows[2],
},
}));
interface ConnectionEmbedProps {
redirectUrl: string;
onConnectionCreated?: (connection: any) => void;
onConnectionUpdated?: (connection: any) => void;
onError?: (error: any) => void;
}
const ConnectionEmbed: FC = ({
redirectUrl,
onConnectionCreated,
onConnectionUpdated,
onError,
}) => {
const classes = useStyles();
const iframeRef = useRef(null);
const [iframeHeight, setIframeHeight] = useState(600);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
// Only process messages from datagrid-api
if (!event.data.type || !event.data.type.includes("datagrid-api")) {
return;
}
console.log("Received iframe message:", event.data);
switch (event.data.type) {
case "datagrid-api/connection-created":
console.log("Connection created:", event.data.payload);
onConnectionCreated?.(event.data.payload);
break;
case "datagrid-api/connection-updated":
console.log("Connection updated:", event.data.payload);
onConnectionUpdated?.(event.data.payload);
break;
case "datagrid-api/error":
console.error("Iframe error:", event.data.payload);
onError?.(event.data.payload);
break;
case "datagrid-api/resize":
const { height, width } = event.data.payload;
setIframeHeight(height);
console.log(`Iframe resized to: ${width}x${height}`);
break;
case "datagrid-api/content-loaded":
console.log("Iframe content loaded");
break;
default:
console.log("Unknown iframe event:", event.data.type);
}
};
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
};
}, [onConnectionCreated, onConnectionUpdated, onError]);
return (
Connect Your Service
);
};
export default ConnectionEmbed;
```
## Connection Embed Usage Example
```tsx theme={null}
import ConnectionEmbed from "./ConnectionEmbed";
function App() {
const [redirectUrl, setRedirectUrl] = useState("");
useEffect(() => {
const initConnection = async () => {
try {
// Use createConnectionRedirectUrl to get the redirect URL
const { redirect_url } = await datagridClient.connections.createConnectionRedirectUrl({
connector_id: "google_drive",
});
setRedirectUrl(redirect_url);
} catch (err) {
console.error("Failed to create connection:", err);
}
};
initConnection();
}, []);
const handleConnectionCreated = (connection) => {
console.log("New connection:", connection);
// Handle the newly created connection
// You can store it, redirect user, etc.
};
const handleError = (error) => {
console.error("Connection error:", error);
// Handle any errors that occur during connection creation
};
if (!redirectUrl) {
return ;
}
return (
);
}
```
***
## Knowledge from Connection Embed
After creating a connection, you can use it to import data as knowledge for your agents. The `KnowledgeFromConnectionEmbed` component handles this flow.
### KnowledgeFromConnectionEmbed Component
This component receives the `redirect_url` from the `createKnowledgeFromConnection` API call:
```tsx theme={null}
import React, { FC, useRef, useEffect, useState } from "react";
import { Typography, Box } from "@mui/material";
import makeStyles from "@mui/styles/makeStyles";
const useStyles = makeStyles((theme) => ({
root: {
height: "100%",
width: "100%",
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
},
iframeWrapper: {
display: "flex",
flexDirection: "column",
gap: theme.spacing(4),
},
iframe: {
border: "none",
borderRadius: theme.spacing(1),
boxShadow: theme.shadows[2],
},
}));
interface KnowledgeFromConnectionEmbedProps {
redirectUrl: string;
onKnowledgeCreated?: (payload: { knowledge_id: string }) => void;
onError?: (error: { message: string; error: object | null }) => void;
}
const KnowledgeFromConnectionEmbed: FC = ({
redirectUrl,
onKnowledgeCreated,
onError,
}) => {
const classes = useStyles();
const iframeRef = useRef(null);
const [iframeHeight, setIframeHeight] = useState(600);
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
// Only process messages from datagrid-api
if (!event.data.type || !event.data.type.includes("datagrid-api")) {
return;
}
console.log("Received iframe message:", event.data);
switch (event.data.type) {
case "datagrid-api/knowledge-created":
console.log("Knowledge created:", event.data.payload);
onKnowledgeCreated?.(event.data.payload);
break;
case "datagrid-api/error":
console.error("Iframe error:", event.data.payload);
onError?.(event.data.payload);
break;
case "datagrid-api/resize":
const { height, width } = event.data.payload;
setIframeHeight(height);
console.log(`Iframe resized to: ${width}x${height}`);
break;
case "datagrid-api/content-loaded":
console.log("Iframe content loaded");
break;
default:
console.log("Unknown iframe event:", event.data.type);
}
};
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
};
}, [onKnowledgeCreated, onError]);
return (
Select Data to Import
);
};
export default KnowledgeFromConnectionEmbed;
```
### Knowledge from Connection Usage Example
```tsx theme={null}
import KnowledgeFromConnectionEmbed from "./KnowledgeFromConnectionEmbed";
function App() {
const [redirectUrl, setRedirectUrl] = useState("");
const connectionId = "conn_abc123"; // The connection ID from a previously created connection
useEffect(() => {
const initKnowledge = async () => {
try {
// Use createKnowledgeFromConnection to get the redirect URL
// For display purposes, the datagrid API is invoked inline
// In a production setting, this needs to be proxied through your backend to avoid leaking any secrets
const { redirect_url } = await datagridClient.knowledge.createKnowledgeFromConnection({
connection_id: connectionId,
});
setRedirectUrl(redirect_url);
} catch (err) {
console.error("Failed to initiate knowledge creation:", err);
}
};
initKnowledge();
}, [connectionId]);
const handleKnowledgeCreated = (payload: { knowledge_id: string }) => {
console.log("Knowledge created with ID:", payload.knowledge_id);
// Handle the newly created knowledge
// You can fetch knowledge details, redirect user, etc.
};
const handleError = (error) => {
console.error("Knowledge creation error:", error);
// Handle any errors that occur during knowledge creation
};
if (!redirectUrl) {
return ;
}
return (
);
}
```
### Complete Flow Example
Here's an example showing the complete flow from connection creation to knowledge import:
```tsx theme={null}
import { useState } from "react";
import ConnectionEmbed from "./ConnectionEmbed";
import KnowledgeFromConnectionEmbed from "./KnowledgeFromConnectionEmbed";
function ConnectionToKnowledgeFlow() {
const [step, setStep] = useState<"connection" | "knowledge">("connection");
const [connectionRedirectUrl, setConnectionRedirectUrl] = useState("");
const [knowledgeRedirectUrl, setKnowledgeRedirectUrl] = useState("");
const [connectionId, setConnectionId] = useState("");
// Step 1: Initialize connection creation
useEffect(() => {
const initConnection = async () => {
const { redirect_url } = await datagridClient.connections.createConnectionRedirectUrl({
connector_id: "google_drive",
});
setConnectionRedirectUrl(redirect_url);
};
initConnection();
}, []);
// Step 2: When connection is created, get knowledge redirect URL
const handleConnectionCreated = async (connection) => {
console.log("Connection created:", connection.id);
setConnectionId(connection.id);
// Get the redirect URL for knowledge creation
// For display purposes, the datagrid API is invoked inline
// In a production setting, this needs to be proxied through your backend to avoid leaking any secrets
const { redirect_url } = await datagridClient.knowledge.createKnowledgeFromConnection({
connection_id: connection.id,
});
setKnowledgeRedirectUrl(redirect_url);
setStep("knowledge");
};
// Step 3: Handle knowledge creation completion
const handleKnowledgeCreated = (payload) => {
console.log("Knowledge created:", payload.knowledge_id);
// Flow complete! The agent can now use this knowledge.
};
if (step === "connection" && connectionRedirectUrl) {
return (
);
}
if (step === "knowledge" && knowledgeRedirectUrl) {
return (
);
}
return
Loading...
;
}
```
## Message Events
The iframe will send various postMessage events to the parent window. All events follow this structure:
```typescript theme={null}
interface IFrameEvent {
type: string;
payload: any;
}
```
### Available Event Types
#### Connection Events
##### `datagrid-api/connection-created`
Emitted when a new connection is successfully created. Used with the `ConnectionEmbed` component.
**Payload:**
```typescript theme={null}
{
object: "connection",
id: string,
name: string,
teamspace_id: string,
connector_id: string,
valid: boolean,
value: string,
created_at: string,
updated_at: string
}
```
##### `datagrid-api/connection-updated`
Emitted when an existing connection is successfully updated.
**Payload:** Same as connection-created event.
#### Knowledge Events
##### `datagrid-api/knowledge-created`
Emitted when knowledge is successfully created from a connection. Used with the `KnowledgeFromConnectionEmbed` component.
**Payload:**
```typescript theme={null}
{
knowledge_id: string
}
```
#### Common Events
##### `datagrid-api/error`
Emitted when an error occurs during the connection or knowledge creation process.
**Payload:**
```typescript theme={null}
{
message: string,
error: object | null
}
```
##### `datagrid-api/resize`
Emitted when the iframe needs to be resized to accommodate content.
**Payload:**
```typescript theme={null}
{
height: number,
width: number
}
```
##### `datagrid-api/content-loaded`
Emitted when the iframe content has finished loading.
**Payload:** `null`
## Security Considerations
* Always validate the origin of postMessage events to ensure they come from your expected domain
* Implement proper error handling for all message types
* Consider implementing a timeout mechanism for connection creation
* Store connection credentials securely after successful creation
## Best Practices
1. **Event Filtering**: Always check that messages are from the expected source and contain the `datagrid-api` prefix
2. **Error Handling**: Implement comprehensive error handling for all possible error scenarios
3. **User Feedback**: Provide clear feedback to users about the connection status
4. **Responsive Design**: Handle iframe resizing events to provide a smooth user experience
5. **Loading States**: Show appropriate loading states while the iframe is initializing
## Summary
| Component | API Endpoint | Event on Success |
| ------------------------------ | ------------------------------------------- | --------------------------------- |
| `ConnectionEmbed` | `connections.createConnectionRedirectUrl()` | `datagrid-api/connection-created` |
| `KnowledgeFromConnectionEmbed` | `knowledge.createKnowledgeFromConnection()` | `datagrid-api/knowledge-created` |
### Quick Reference
**Creating a Connection:**
```typescript theme={null}
// 1. Get redirect URL
const { redirect_url } = await datagridClient.connections.createConnectionRedirectUrl({
connector_id: "google_drive",
});
// 2. Pass to ConnectionEmbed component
```
**Creating Knowledge from Connection:**
```typescript theme={null}
// 1. Get redirect URL (requires an existing connection_id)
const { redirect_url } = await datagridClient.knowledge.createKnowledgeFromConnection({
connection_id: "conn_abc123",
});
// 2. Pass to KnowledgeFromConnectionEmbed component
```
# Creating Data Views
Source: https://developers.datagrid.com/introduction/data-views
Learn how to share knowledge with Data Views and Service Accounts
# Overview
Integrate your data with tools like Power BI, Tableau, and Looker by creating Data Views of your Datagrid Knowledge, enabling seamless connections and visualization without the hassle.
## Complete Implementation
Here's a complete implementation showing the full flow from creating knowledge to obtaining BigQuery connector credentials:
```python Python theme={null}
import os
from datagrid_ai import Datagrid
datagrid_client = Datagrid(
api_key=os.environ.get("DATAGRID_API_KEY"),
)
# Step 1: Create Knowledge from file(s)
with open("./data/sales-q4.csv", "rb") as file:
knowledge = datagrid_client.knowledge.create(
name="Sales Data Q4",
files=[file],
)
# Step 2: Create a Service Account
service_account = datagrid_client.data_views.service_accounts.create(
name="bigquery-connector",
type="gcp",
)
# Step 3: Create a Data View linking knowledge to service account
data_view = datagrid_client.data_views.create(
knowledge_id=knowledge.id,
service_account_id=service_account.id,
name="Sales Data View",
)
# Step 4: Get Service Account Credentials to view your Data Views via a BigQuery connector
credentials = datagrid_client.data_views.service_accounts.credentials(
service_account.id
)
```
```typescript TypeScript theme={null}
import Datagrid from "datagrid-ai";
import * as fs from "fs";
const datagridClient = new Datagrid({
apiKey: process.env["DATAGRID_API_KEY"],
});
// Step 1: Create Knowledge from file(s)
const knowledge = await datagridClient.knowledge.create({
name: "Sales Data Q4",
files: [fs.createReadStream("./data/sales-q4.csv")],
});
// Step 2: Create a Service Account
const serviceAccount = await datagridClient.dataViews.serviceAccounts.create({
name: "bigquery-connector",
type: "gcp",
});
// Step 3: Create a Data View linking knowledge to service account
const dataView = await datagridClient.dataViews.create({
knowledge_id: knowledge.id,
service_account_id: serviceAccount.id,
name: "Sales Data View",
});
// Step 4: Get Service Account Credentials to view your Data Views via a BigQuery connector
const credentials = await datagridClient.dataViews.serviceAccounts.credentials(
serviceAccount.id
);
```
# Quickstart
Source: https://developers.datagrid.com/introduction/quickstart
Start using Datagrid in under 5 minutes
Datagrid lets you ingest data from 100+ connectors and build AI Agents on top of it.
## Get your API Key
Head over to the [Datagrid console](https://app.datagrid.com/chats?at=chats\&st=api_keys\&redirect_url=chats?at=chats%26st=api_keys) to claim your API key.
Once you've claimed your API key, export it as an environment variable.
```bash macOS / Linux theme={null}
export DATAGRID_API_KEY="your_api_key_here"
```
```bash Windows theme={null}
setx DATAGRID_API_KEY "your_api_key_here"
```
## Install the SDK
We offer incredibly easy-to-use client SDKs for [Python](https://github.com/DatagridAI/datagrid-python) and [Typescript/Javascript](https://github.com/DatagridAI/datagrid-node)
```python Python theme={null}
pip install datagrid_ai
```
```javascript JavaScript theme={null}
npm install datagrid-ai
```
## Make your first request
If you use custom MCP servers, run in **Execute** mode by setting `chat_mode: "full_agent"` so the agent can plan and execute tool calls reliably.
```python Python theme={null}
import os
from datagrid_ai import Datagrid
client = Datagrid(
# This is the default and can be omitted
api_key=os.environ.get("DATAGRID_API_KEY"),
)
response = client.converse(
prompt="Hello world!",
chat_mode="full_agent",
)
print(response.content[0].text)
```
```javascript JavaScript theme={null}
import Datagrid from 'datagrid-ai';
const client = new Datagrid({
// This is the default and can be omitted
apiKey: process.env['DATAGRID_API_KEY'],
});
const response = await client.converse({
prompt: 'Hello world!',
chat_mode: 'full_agent',
});
console.log(response.content[0].text);
```
## Rate limits
API endpoints are rate-limited per teamspace over a 60-second sliding window. The default limit is 200 requests, but some endpoints enforce stricter limits. Every response includes `X-RateLimit-Limit` and `X-RateLimit-Remaining` so you can monitor usage. See the [Rate Limits](/api-reference/rate-limits) page for details on tiers, headers, 429 responses, and retry best practices.
# Knowledge upload limits
Source: https://developers.datagrid.com/knowledge/upload-limits
File size limits for the Knowledge API, how uploads are handled, and strategies for working with large documents.
When uploading files to Knowledge using `POST /v1/knowledge` or `PATCH /v1/knowledge/{id}`, each file must stay within your plan’s **per-file size limit**.
These limits help ensure consistent processing performance, reliable uploads, and fair usage across all users.
## Per-file upload size by plan
Your upload limit depends on your current subscription plan.
| Plan | Per-file limit |
| ---------- | -------------- |
| Free | 5 MB |
| Pro | 300 MB |
| Enterprise | 300 MB |
Limits apply **per file**, not per request. If you upload multiple files at once, each file must individually meet the size requirement.
If you need to upload larger files, consider upgrading your plan or contacting support.
## What happens if a file is too large?
If a file exceeds your plan’s limit, the API will return a **413 — `payload_too_large`** error.
This means the file could not be processed because it exceeds the maximum allowed size.
## Supported formats
Knowledge supports a variety of file types, including PDFs, Office documents, CSVs, images, and more.
For a complete list, see [Create knowledge](/api-reference/knowledge/create-knowledge).
## Working with large documents
If your content exceeds the upload limit, here are some effective approaches:
* **Split large files**\
Break documents into smaller sections so each file fits within the limit. This is the most reliable solution.
* **Compress files when possible**\
Reducing file size (especially for images or PDFs) can help you stay within limits without losing important content.
* **Upgrade your plan**\
Higher-tier plans support significantly larger file uploads. Reach out to our support team to request a subscription upgrade: [support@datagrid.com](mailto:support@datagrid.com)
## Related limits
For request-level limits such as API rate limiting, see [Rate limits](/api-reference/rate-limits).