Skip to content
DocsAPI ReferenceData API

Data API

CRUD operations for entities via HTTP

The Data API lets you create, read, update, and delete entities in your Struere data layer over HTTP. Use it to integrate your entity database with any application, backend service, or automation tool.

Authentication

Data API endpoints require an API key with the data or * (wildcard) permission. Keys created in the dashboard default to * which grants access to all endpoints including the Data API.

Authorization: Bearer sk_dev_abc123...

The API key determines the environment (development or production). All operations are scoped to that environment.

Endpoints

Method Path Description
GET /v1/entity-types List entity types
GET /v1/data/:type List entities
GET /v1/data/:type/:id Get entity by ID
POST /v1/data/:type Create entity
POST /v1/data/:type/query Query with filters
POST /v1/data/:type/search Full-text search
PATCH /v1/data/:type/:id Update entity
DELETE /v1/data/:type/:id Delete entity

Rate Limits

Data API endpoints are rate-limited separately from Chat:

Scope Limit
Per API key 60 requests/minute
Per organization 200 requests/minute

Response Shape

All entity responses share the same shape:

{
  "id": "k17abc...",
  "type": "customer",
  "status": "active",
  "data": { "name": "Jane", "email": "jane@co.com" },
  "createdAt": 1710000000000,
  "updatedAt": 1710000000000
}
Field Type Description
id string The entity's Convex document ID
type string The entity type slug
status string Entity status (e.g., active, archived)
data object The entity's data fields
createdAt number Creation timestamp (Unix ms)
updatedAt number Last update timestamp (Unix ms)

List, query, and search endpoints return a paginated wrapper:

{
  "data": [ ... ],
  "cursor": "k17abc...",
  "hasMore": true
}

GET /v1/entity-types

List all entity types in the current environment.

Response

{
  "data": [
    {
      "slug": "customer",
      "name": "Customer",
      "schema": { "name": "string", "email": "string" },
      "searchFields": ["name", "email"]
    }
  ]
}

Examples

curl

curl https://your-deployment.convex.site/v1/entity-types \
  -H "Authorization: Bearer sk_dev_abc123"

Python

import requests

response = requests.get(
    "https://your-deployment.convex.site/v1/entity-types",
    headers={"Authorization": "Bearer sk_dev_abc123"},
)
types = response.json()["data"]
for t in types:
    print(f"{t['slug']}: {t['name']}")

GET /v1/data/:type

List entities of a given type with cursor-based pagination.

Query Parameters

Parameter Type Default Description
limit number 50 Max results per page (1–100)
cursor string Cursor from a previous response for the next page
status string Filter by status (e.g., active, archived)

Response

{
  "data": [
    {
      "id": "k17abc...",
      "type": "customer",
      "status": "active",
      "data": { "name": "Jane", "email": "jane@co.com" },
      "createdAt": 1710000000000,
      "updatedAt": 1710000000000
    }
  ],
  "cursor": "k17xyz...",
  "hasMore": true
}

Examples

curl

curl "https://your-deployment.convex.site/v1/data/customer?limit=10" \
  -H "Authorization: Bearer sk_dev_abc123"

Pagination

curl "https://your-deployment.convex.site/v1/data/customer?limit=10&cursor=k17xyz..." \
  -H "Authorization: Bearer sk_dev_abc123"

Python

import requests

BASE = "https://your-deployment.convex.site"
HEADERS = {"Authorization": "Bearer sk_dev_abc123"}

all_customers = []
cursor = None

while True:
    params = {"limit": 50}
    if cursor:
        params["cursor"] = cursor

    response = requests.get(f"{BASE}/v1/data/customer", headers=HEADERS, params=params)
    result = response.json()
    all_customers.extend(result["data"])

    if not result["hasMore"]:
        break
    cursor = result["cursor"]

print(f"Total: {len(all_customers)}")

GET /v1/data/:type/:id

Get a single entity by its ID.

Response

{
  "id": "k17abc...",
  "type": "customer",
  "status": "active",
  "data": { "name": "Jane", "email": "jane@co.com" },
  "createdAt": 1710000000000,
  "updatedAt": 1710000000000
}

Examples

curl

curl https://your-deployment.convex.site/v1/data/customer/k17abc... \
  -H "Authorization: Bearer sk_dev_abc123"

TypeScript

const response = await fetch(
  "https://your-deployment.convex.site/v1/data/customer/k17abc...",
  { headers: { Authorization: "Bearer sk_dev_abc123" } }
)
const customer = await response.json()
console.log(customer.data.name)

POST /v1/data/:type

Create a new entity.

Request

Headers:

Header Value
Authorization Bearer YOUR_API_KEY
Content-Type application/json

Body:

{
  "data": {
    "name": "Jane Doe",
    "email": "jane@example.com",
    "plan": "pro"
  },
  "status": "active"
}
Field Type Required Description
data object Yes The entity's data fields
status string No Initial status (defaults to active)

Response (201 Created)

{
  "id": "k17abc...",
  "type": "customer",
  "status": "active",
  "data": { "name": "Jane Doe", "email": "jane@example.com", "plan": "pro" },
  "createdAt": 1710000000000,
  "updatedAt": 1710000000000
}

Examples

curl

curl -X POST https://your-deployment.convex.site/v1/data/customer \
  -H "Authorization: Bearer sk_dev_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "name": "Jane Doe",
      "email": "jane@example.com",
      "plan": "pro"
    }
  }'

Python

import requests

response = requests.post(
    "https://your-deployment.convex.site/v1/data/customer",
    headers={
        "Authorization": "Bearer sk_dev_abc123",
        "Content-Type": "application/json",
    },
    json={
        "data": {
            "name": "Jane Doe",
            "email": "jane@example.com",
            "plan": "pro",
        }
    },
)
customer = response.json()
print(f"Created: {customer['id']}")

TypeScript

const response = await fetch("https://your-deployment.convex.site/v1/data/customer", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk_dev_abc123",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    data: { name: "Jane Doe", email: "jane@example.com", plan: "pro" },
  }),
})
const customer = await response.json()
console.log(`Created: ${customer.id}`)

POST /v1/data/:type/query

Query entities with filters. Filters support comparison operators for advanced queries.

Request

Body:

{
  "filters": {
    "plan": "pro",
    "age": { "$gte": 18 },
    "status": { "$in": ["active", "trial"] }
  },
  "status": "active",
  "limit": 25,
  "cursor": "k17xyz..."
}
Field Type Required Description
filters object No Field-level filters (see operators below)
status string No Filter by entity status
limit number No Max results (default 50, max 100)
cursor string No Pagination cursor from previous response

Filter Operators

Operator Description Example
(none) Exact match { "plan": "pro" }
$in Value in array { "plan": { "$in": ["pro", "enterprise"] } }
$nin Value not in array { "plan": { "$nin": ["free"] } }
$ne Not equal { "status": { "$ne": "archived" } }
$gt Greater than { "age": { "$gt": 18 } }
$gte Greater than or equal { "age": { "$gte": 18 } }
$lt Less than { "score": { "$lt": 50 } }
$lte Less than or equal { "score": { "$lte": 100 } }

Response

{
  "data": [ ... ],
  "cursor": "k17xyz...",
  "hasMore": false
}

Examples

curl

curl -X POST https://your-deployment.convex.site/v1/data/customer/query \
  -H "Authorization: Bearer sk_dev_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "plan": "pro",
      "age": { "$gte": 18 }
    },
    "limit": 10
  }'

Python

import requests

response = requests.post(
    "https://your-deployment.convex.site/v1/data/customer/query",
    headers={
        "Authorization": "Bearer sk_dev_abc123",
        "Content-Type": "application/json",
    },
    json={
        "filters": {"plan": "pro", "age": {"$gte": 18}},
        "limit": 10,
    },
)
result = response.json()
for customer in result["data"]:
    print(customer["data"]["name"])

POST /v1/data/:type/search

Full-text search across an entity type's search fields. Search fields are defined in the entity type's configuration.

Request

Body:

{
  "query": "jane",
  "limit": 10
}
Field Type Required Description
query string Yes Search text
limit number No Max results (default 20, max 100)

Response

{
  "data": [
    {
      "id": "k17abc...",
      "type": "customer",
      "status": "active",
      "data": { "name": "Jane Doe", "email": "jane@example.com" },
      "createdAt": 1710000000000,
      "updatedAt": 1710000000000
    }
  ]
}

Examples

curl

curl -X POST https://your-deployment.convex.site/v1/data/customer/search \
  -H "Authorization: Bearer sk_dev_abc123" \
  -H "Content-Type: application/json" \
  -d '{ "query": "jane", "limit": 10 }'

TypeScript

const response = await fetch(
  "https://your-deployment.convex.site/v1/data/customer/search",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer sk_dev_abc123",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query: "jane", limit: 10 }),
  }
)
const { data } = await response.json()
data.forEach((customer: any) => console.log(customer.data.name))

PATCH /v1/data/:type/:id

Update an entity. Data fields are shallow-merged with the existing data.

Request

Headers:

Header Value
Authorization Bearer YOUR_API_KEY
Content-Type application/json

Body:

{
  "data": {
    "plan": "enterprise",
    "notes": "Upgraded from pro"
  },
  "status": "active"
}
Field Type Required Description
data object Yes Fields to update (shallow merge)
status string No New status value

Response

Returns the full updated entity.

{
  "id": "k17abc...",
  "type": "customer",
  "status": "active",
  "data": { "name": "Jane Doe", "email": "jane@example.com", "plan": "enterprise", "notes": "Upgraded from pro" },
  "createdAt": 1710000000000,
  "updatedAt": 1710000050000
}

Examples

curl

curl -X PATCH https://your-deployment.convex.site/v1/data/customer/k17abc... \
  -H "Authorization: Bearer sk_dev_abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "data": { "plan": "enterprise" }
  }'

Python

import requests

response = requests.patch(
    "https://your-deployment.convex.site/v1/data/customer/k17abc...",
    headers={
        "Authorization": "Bearer sk_dev_abc123",
        "Content-Type": "application/json",
    },
    json={"data": {"plan": "enterprise"}},
)
updated = response.json()
print(f"Updated plan: {updated['data']['plan']}")

DELETE /v1/data/:type/:id

Soft-delete an entity. The entity's status is set to deleted and it will no longer appear in list or query results.

Response

{
  "success": true
}

Examples

curl

curl -X DELETE https://your-deployment.convex.site/v1/data/customer/k17abc... \
  -H "Authorization: Bearer sk_dev_abc123"

TypeScript

const response = await fetch(
  "https://your-deployment.convex.site/v1/data/customer/k17abc...",
  {
    method: "DELETE",
    headers: { Authorization: "Bearer sk_dev_abc123" },
  }
)
const result = await response.json()
console.log(result.success)

Error Handling

Status Codes

Status Meaning Common Causes
200 Success Request completed
201 Created Entity created (POST)
400 Bad Request Missing required fields, invalid path
401 Unauthorized Missing or invalid API key
403 Forbidden API key lacks data permission, or entity outside permission scope
404 Not Found Entity or entity type not found
429 Too Many Requests Rate limit exceeded (check Retry-After header)
500 Internal Error Server-side failure

Error Response Format

{
  "error": "Error description"
}

Rate limit errors include a Retry-After header with the number of seconds to wait:

{
  "error": "Rate limit exceeded",
  "retryAt": 1710000060000
}

Handling Errors in Code

TypeScript:

const response = await fetch("https://your-deployment.convex.site/v1/data/customer", {
  headers: { Authorization: "Bearer sk_dev_abc123" },
})

if (!response.ok) {
  const error = await response.json()
  switch (response.status) {
    case 401:
      throw new Error("Invalid API key")
    case 403:
      throw new Error("Missing data permission")
    case 404:
      throw new Error("Not found")
    case 429:
      const retryAfter = response.headers.get("Retry-After")
      throw new Error(`Rate limited, retry in ${retryAfter}s`)
    default:
      throw new Error(`Error: ${error.error}`)
  }
}

const data = await response.json()

Python:

import requests
import time

response = requests.get(
    "https://your-deployment.convex.site/v1/data/customer",
    headers={"Authorization": "Bearer sk_dev_abc123"},
)

if response.status_code == 429:
    retry_after = int(response.headers.get("Retry-After", 5))
    time.sleep(retry_after)
elif response.status_code == 401:
    raise Exception("Invalid API key")
elif response.status_code == 403:
    raise Exception("API key lacks data permission")
elif response.status_code >= 400:
    raise Exception(f"Error: {response.json()['error']}")

data = response.json()