Databases
A high-performance document database built on OpenSearch. Supports schema definition, CRUD, full-text search, and vector search.
Key Features
- Schema definition: field types, indexing options, vector field configuration
- CRUD operations: single/bulk document create, read, update, delete
- Full-text search: OpenSearch Query DSL support (match, term, bool, range)
- Vector search: k-NN semantic search (Pro plan and above)
- Aggregations: terms, date_histogram, statistical aggregations
Credit Costs
Database operations deduct credits by type. Credits are automatically refunded if an operation fails.
| Operation | Endpoint | Credits |
|---|---|---|
| Create document | POST /documents | 0.1 |
| Get document | GET /documents/{id} | 0.05 |
| Update document | PUT, PATCH /documents/{id} | 0.1 |
| Delete document | DELETE /documents/{id} | 0.05 |
| Bulk create | POST /documents/_bulk | 5.0 / 100 docs |
| Bulk delete | POST /documents/_bulk_delete | 0.05 / doc |
| Bulk update | POST /documents/_bulk_update | 0.1 / doc |
| Full-text search | POST /search | 0.5 |
| Aggregation | POST /aggregate | 1.0 |
| Vector search | POST /search/vector | 2.0 |
Free: Database create/delete, list, schema changes, stats, document listing, and document count (_count) do not deduct credits.
Plan Limits
Database resource limits vary by subscription plan. The table is for the shared cluster; larger footprints are served on a dedicated node (contact us). When the cluster runs out of headroom, writes may be refused temporarily with 503 database_capacity.
| Item | Free | Pro | Team | Enterprise |
|---|---|---|---|---|
| Max databases | 1 | 5 | 20 | 100 |
| Max documents per DB | 10,000 | 500,000 | 5,000,000 | 20,000,000 |
| Max storage | 100 MB | 2 GB | 10 GB | 50 GB |
| Max document size | 100 KB | 1 MB | 10 MB | 25 MB |
| Max bulk operation size | 100 | 1,000 | 10,000 | 100,000 |
| Vector search | - | 1,536 dims | 2,048 dims | 4,096 dims |
| Requests per minute (read / search / write, per team) | 300 / 60 / 120 | 1,500 / 300 / 600 | 5,000 / 1,000 / 2,000 | 15,000 / 3,000 / 6,000 |
Create a Database
Create a new database with a schema. The database name must be unique within the team.
Naming Rules
- Must start with a lowercase letter
- Only lowercase letters, digits, hyphens (
-), and underscores (_) are allowed - 3–50 characters
aiapi_,db_system_,_cannot be used as a prefix (reserved prefixes)
curl -X POST https://api.core.today/v1/databases \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "products",
"display_name": "Product Catalog",
"description": "E-commerce product database",
"schema_fields": {
"title": {"type": "text", "index": true},
"description": {"type": "text", "index": true},
"price": {"type": "float"},
"category": {"type": "keyword"},
"tags": {"type": "keyword"},
"in_stock": {"type": "boolean"},
"created_at": {"type": "date"}
}
}'Example Response
{
"database_uid": "db_abc123",
"name": "products",
"display_name": "Product Catalog",
"description": "E-commerce product database",
"team_id": "team_xyz",
"schema_fields": {...},
"document_count": 0,
"created_at": "2024-12-26T10:00:00Z"
}How Do I Edit or Delete a Schema?
A common question when a schema was defined incorrectly. Because of how the search index (OpenSearch mapping) works, fields that already exist cannot be changed. Here is what is and isn't possible.
- ✅ Add new fields: Always possible via the add-fields API (POST /databases/{uid}/fields) — free. If you only missed a field, use this instead of deleting the database.
- ✅ Edit name/description: The display_name and description can be updated via PATCH /databases/{uid} — free.
- ❌ Change an existing field's type: The type of an already-defined field (e.g. text → keyword) cannot be changed.
- ❌ Remove an existing field: Removing individual fields from the schema is not supported.
Made a mistake in the schema? Delete and recreate
- If you have documents to keep, export them first (e.g. via the search API). Deleting a database deletes all of its documents.
- Create a new database with the correct schema and re-insert the exported documents using the bulk create API (_bulk).
- After verifying the new database works, delete the old one. Deletion is irreversible.
You can also delete a database from the console: team workspace → Databases page. Adding fields and editing the name/description are currently API-only.
Supported Field Types
| Type | Description | Example |
|---|---|---|
text | Full-text searchable text. Gets a .keyword sub-field for sorting/aggregations automatically (plain field names in sort/aggs are rewritten); analyzer chosen by language (ko=nori · en · ja · zh) or analyzer | "Product description..." |
keyword | String for exact matching (filters, aggregations) | "electronics" |
integer | Integer | 42 |
long | 64-bit integer | 9223372036854775807 |
float | 32-bit floating point | 19.99 |
double | 64-bit floating point | 3.141592653589793 |
boolean | true/false | true |
date | ISO 8601 date | "2024-12-26T10:00:00Z" |
object | Nested object (indexing can be turned on/off) | {"key": "value"} |
nested | Array of independently queryable objects | [{"name": "A"}, ...] |
knn_vector | Embedding for vector search (Pro+) | [0.1, 0.2, ...] |
geo_point | Location {"lat", "lon"} — geo_distance queries, where $near | {"lat": 37.5, "lon": 127.0} |
ip | IPv4/IPv6 address — term queries accept CIDR ("10.0.0.0/8") | "10.0.0.5" |
date_range | Date interval {"gte", "lte"} — range queries intersect | {"gte": "2026-01-01", "lte": "2026-01-31"} |
Language (analyzer) settings
Pass language when creating a database and every text field defaults to that analyzer (ko → nori). A per-field language/analyzer wins. An existing field's analyzer cannot be changed (needs a rebuild); the setting applies to fields added afterwards.
{
"name": "articles",
"language": "ko",
"schema_fields": {
"title": { "type": "text" },
"summary": { "type": "text", "language": "en" },
"status": { "type": "keyword" }
}
}Synonyms and refresh interval
`synonyms` are search-time rules in Solr format — "usa, united states" (equivalent) or "phone => mobile" (one-way) — applied to every text field through a per-language analyzer (ko/en/ja/zh), so documents are not re-indexed. Because a managed cluster cannot close an index, changing them later goes through POST /reindex (schema evolution). `refresh_interval` (1s … 60s, default 1s) is the search visibility bound; raising it on write-heavy databases improves indexing throughput and PATCH applies it immediately.
POST /v1/databases
{
"name": "places",
"language": "ko",
"synonyms": ["휴대폰, 핸드폰, 스마트폰", "usa, united states", "phone => mobile"],
"refresh_interval": "5s",
"schema_fields": {
"name": {"type": "text"},
"loc": {"type": "geo_point"},
"ip": {"type": "ip"},
"open": {"type": "date_range"}
}
}
PATCH /v1/databases/{database_uid} {"refresh_interval": "30s"}
POST /v1/databases/{database_uid}/reindex {"synonyms": ["usa, united states, u.s."]}
POST /v1/databases/{database_uid}/search {"where": {"loc": {"$near": {"lat": 37.5, "lon": 127.0, "distance": "5km"}}}}Vector Field Configuration
A field for vector search must specify dimension and space_type:
"embedding": {
"type": "knn_vector",
"dimension": 1536,
"space_type": "cosinesimil"
}Document CRUD
Automatic Metadata (_meta)
Every document automatically gets a _meta field:
_meta.created_at— document creation time (ISO 8601)_meta.updated_at— last update time (ISO 8601)_meta.created_by_api_key— UID of the API key used to create it
Create Document
docs.databases.crud.createCodeCreate-only: POST with an id that already exists returns 409 (document_already_exists) instead of overwriting. Use PUT/PATCH (with ?upsert=true if needed) to change an existing document. _bulk still overwrites existing ids.
Bulk Create Documents
curl -X POST https://api.core.today/v1/databases/{database_uid}/documents/_bulk \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{"id": "product-001", "data": {"title": "Product 1", "price": 99.99}},
{"id": "product-002", "data": {"title": "Product 2", "price": 149.99}},
{"id": "product-003", "data": {"title": "Product 3", "price": 199.99}}
]
}'Response: Returns the success/failure counts and error details.
Get Document
# Get a specific document
curl https://api.core.today/v1/databases/{database_uid}/documents/{doc_id} \
-H "X-API-Key: cdt_your_api_key"
# Get specific fields only (Field Projection)
curl "https://api.core.today/v1/databases/{database_uid}/documents/{doc_id}?_source=title,price" \
-H "X-API-Key: cdt_your_api_key"
# List documents (pagination)
curl "https://api.core.today/v1/databases/{database_uid}/documents?size=20&from=0" \
-H "X-API-Key: cdt_your_api_key"
# List documents - specific fields only
curl "https://api.core.today/v1/databases/{database_uid}/documents?size=20&_source=title,price" \
-H "X-API-Key: cdt_your_api_key"_source: Specify a comma-separated list of fields to include only those in the response. Omit it to return all fields.
Update Document
docs.databases.crud.updateCodeUpsert: PUT/PATCH return 404 when the document does not exist. Add ?upsert=true to create it when missing and update it when present — this replaces the "PATCH first, POST on 404" pattern; the response's result field tells you which happened. _bulk_update accepts ?upsert=true too.
Consistency model
Reads, updates and deletes by ID are immediately consistent after any write. search, list, _count and aggregate reflect a write within about 1 second (eventual). Every write endpoint accepts ?refresh=false|true|wait_for (default false). Use refresh=true only when you must search immediately after writing — it adds up to ~1s of latency to that request.
Durability & recovery
Every acknowledged write is stored in S3 first (one object per document, conditional PUT) and only then indexed; the search index is a query layer that can be rebuilt from S3 at any time (POST /reindex, no downtime). A daily audit compares S3 and index document counts per database and alerts operations on any mismatch. Deletes remove the document from both (there is no trash) — use POST /export first if you need a copy.
Delete Document
curl -X DELETE https://api.core.today/v1/databases/{database_uid}/documents/{doc_id} \
-H "X-API-Key: cdt_your_api_key"Bulk Delete
curl -X POST https://api.core.today/v1/databases/{database_uid}/documents/_bulk_delete \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"ids": ["product-001", "product-002", "product-003"]
}'Credits: 0.05 × document count. Non-existent IDs are reported as errors.
Bulk Update
curl -X POST https://api.core.today/v1/databases/{database_uid}/documents/_bulk_update \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"documents": [
{"id": "product-001", "data": {"price": 179.99, "in_stock": false}},
{"id": "product-002", "data": {"price": 129.99}},
{"id": "product-003", "data": {"category": "accessories"}}
]
}'Credits: 0.1 × document count. Behaves as a partial update (PATCH); only the specified fields are updated.
Export / import (API)
# Export everything to a gzip NDJSON file (async, free) — poll for download_url (valid 7 days)
curl -X POST https://api.core.today/v1/databases/{database_uid}/export -H "X-API-Key: cdt_your_api_key"
curl https://api.core.today/v1/databases/{database_uid}/export -H "X-API-Key: cdt_your_api_key"
# → {"export": {"status": "done", "docs": 1200, "download_url": "https://…", "expires_at": "…"}}
# Import from a URL (NDJSON lines {"id"?, "data"} or plain objects; JSON array; gzip ok; ≤50 MB)
curl -X POST https://api.core.today/v1/databases/{database_uid}/import \
-H "X-API-Key: cdt_your_api_key" -H "Content-Type: application/json" \
-d '{"url": "https://files.core.today/…/products.ndjson.gz", "mode": "upsert"}'
curl https://api.core.today/v1/databases/{database_uid}/import -H "X-API-Key: cdt_your_api_key"Behaviour & credits: Export writes the whole S3 source of truth as gzip NDJSON (one {id, data, _meta} per line) and returns a signed URL valid for 7 days — free (resellers pass X-Customer-Id to export one member's documents). Import fetches the URL server-side (private networks blocked, 50 MB cap), parses it, charges the bulk-create rate (5cr per 100 documents) up front and loads in the background. mode=upsert overwrites existing ids; mode=create reports them as per-item errors. Track progress with GET …/import (done/failed/errors, first 100). One job per database at a time (409 job_already_running).
Schema evolution · reindex
# Rebuild with a new schema (types may change, fields may be dropped) and/or language
curl -X POST https://api.core.today/v1/databases/{database_uid}/reindex \
-H "X-API-Key: cdt_your_api_key" -H "Content-Type: application/json" \
-d '{"language": "ko", "schema_fields": {"title": {"type": "text"}, "price": {"type": "float"}}}'
# → 202 {"status": "started", "reindex": {"status": "running", "docs_done": 0, ...}}
# Poll until done
curl https://api.core.today/v1/databases/{database_uid}/reindex -H "X-API-Key: cdt_your_api_key"Behaviour: POST /fields can only add fields. Type changes, field removals and analyzer (language) changes are done by reindexing: a new index is built from the S3 source of truth — asynchronous (202), and reads/searches keep hitting the old index meanwhile. Writes (create/update/delete/bulk) are refused with 409 database_reindexing until it finishes; retry after Retry-After. On success the record switches to the new index and the old one is dropped; on failure the new index is discarded, the database returns to its previous state and reindex.error says why. Free.
Optimistic concurrency (If-Match)
# Every read/write returns _meta.version (also as the ETag header on GET)
curl https://api.core.today/v1/databases/{database_uid}/documents/task-123 \
-H "X-API-Key: cdt_your_api_key"
# → {"id": "task-123", "data": {...}, "_meta": {"version": "12.3", ...}}
# Write only if nobody changed it since — 412 precondition_failed otherwise
curl -X PATCH https://api.core.today/v1/databases/{database_uid}/documents/task-123 \
-H "X-API-Key: cdt_your_api_key" -H "Content-Type: application/json" \
-H 'If-Match: "12.3"' \
-d '{"data": {"status": "done"}}'Behaviour: _meta.version is a response-only token (<seq_no>.<primary_term>, never stored) returned by GET, search hits and every write. Send it back as If-Match on PUT/PATCH/DELETE: if the document changed since that version the write is refused with 412 precondition_failed (nothing is written), so a read-modify-write race can no longer clobber the earlier change. A malformed token is 400 invalid_if_match (before any charge). Not combinable with upsert-create.
Change-event webhooks
Team webhooks receive document.created / document.updated / document.deleted for single-document calls, and documents.bulk_created / bulk_updated / bulk_deleted (ids capped at 1,000 plus count, with a source field) for bulk, by-query and TTL-sweep changes. Subscribe in Console › Webhooks. Same HMAC signature, retries and delivery history as every other event — sync external systems from these instead of polling.
{
"event": "document.updated",
"team_id": "…", "database_uid": "db_abc123",
"doc_id": "task-123", "result": "updated",
"source": "api", // api | by_query | ttl_sweep
"timestamp": "2026-09-13T02:10:00Z"
}
// bulk: { "event": "documents.bulk_deleted", "count": 1500, "ids": [...1000], "ids_truncated": true }Document expiry (TTL)
# Expire in 1 hour (relative) — works on POST, PUT, PATCH and ?upsert=true
curl -X POST https://api.core.today/v1/databases/{database_uid}/documents \
-H "X-API-Key: cdt_your_api_key" -H "Content-Type: application/json" \
-d '{"id": "session-42", "data": {"state": "open"}, "ttl_seconds": 3600}'
# Absolute expiry (ISO 8601, UTC) / clear an expiry with null
curl -X PATCH https://api.core.today/v1/databases/{database_uid}/documents/session-42 \
-H "X-API-Key: cdt_your_api_key" -H "Content-Type: application/json" \
-d '{"data": {"state": "closed"}, "expires_at": "2026-12-31T00:00:00Z"}'Behaviour: Pass expires_at (absolute) or ttl_seconds (relative, min 60s) and it is stored as _meta.expires_at; an hourly sweep deletes expired documents (S3 source of truth included, no delete credits). Until the next sweep an expired document can still be read/searched — filter on _meta.expires_at if that matters. Sending expires_at: null on PATCH/PUT clears the expiry.
Conditional bulk delete / update (_delete_by_query, _update_by_query)
# Delete every document matching a query (up to max_docs, default 1000)
curl -X POST https://api.core.today/v1/databases/{database_uid}/documents/_delete_by_query \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{"query": {"range": {"created": {"lt": "2026-01-01"}}}, "max_docs": 500}'
# Merge fields into every matching document
curl -X POST https://api.core.today/v1/databases/{database_uid}/documents/_update_by_query \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{"query": {"term": {"status": "queued"}}, "data": {"status": "cancelled"}}'Behaviour & credits: The query first selects the target ids (one search, 0.5cr), then the bulk delete (0.05 × docs) / bulk update (0.1 × docs) runs — S3 source-of-truth sync, reseller scoping and per-item results are the bulk endpoints'. Up to max_docs (default 1000, capped by the plan's bulk size) per call; when the response's matched is larger, call again. With insufficient credits nothing is deleted or updated and you get a 402.
Document Count
# Total document count
curl https://api.core.today/v1/databases/{database_uid}/documents/_count \
-H "X-API-Key: cdt_your_api_key"
# Conditional count (pass JSON via the query parameter)
curl "https://api.core.today/v1/databases/{database_uid}/documents/_count?q=%7B%22term%22%3A%7B%22category%22%3A%22electronics%22%7D%7D" \
-H "X-API-Key: cdt_your_api_key"Free: No credits are deducted. Response: {"count": 1234}
Exclude Fields (_source_excludes)
You can exclude specific fields from the response. _source when used together, is converted into includes/excludes.
docs.databases.crud.excludeCodeSearch
Provides powerful search using the OpenSearch Query DSL.
Full-text Search
curl -X POST https://api.core.today/v1/databases/{database_uid}/search \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"query": {
"match": {
"description": "wireless headphones"
}
},
"size": 10,
"from": 0,
"_source": ["title", "price", "category"]
}'_source: The list of fields to include in the response. Omit it to return all fields.
Simple filters (where)
Most lookups don't need Query DSL. `where` is a plain JSON object compiled server-side into a filter (no scoring) and ANDed with `query`/`filter` when both are given. It works on search, list (`?where=<json>`), vector search, aggregations and the by-query endpoints.
POST /v1/databases/{database_uid}/search
{
"where": {
"category": "electronics",
"price": {"$gte": 100, "$lte": 300},
"status": {"$in": ["active", "preorder"]},
"title": {"$contains": "headphones"},
"deleted_at": {"$exists": false},
"$or": [{"brand": "sony"}, {"rating": {"$gte": 4.5}}]
},
"sort": [{"price": "asc"}],
"size": 20
}
GET /v1/databases/{database_uid}/documents?where={"status":"active"}
POST /v1/databases/{database_uid}/documents/_delete_by_query {"where": {"updated": {"$lt": "2026-01-01"}}}Operators: equality `{"status": "active"}` · `$ne` · `$in`/`$nin` · `$gt`/`$gte`/`$lt`/`$lte` · `$exists` · `$prefix` · `$contains` (phrase on text fields, substring on keyword) · `$match` (analyzed full-text) · `$and`/`$or`/`$not`. Text fields compare exactly via their `.keyword` sub-field. `null` means "missing". Malformed filters are rejected with 400 `invalid_where` before any charge.
Compound Search (Boolean Query)
{
"query": {
"bool": {
"must": [
{"match": {"description": "headphones"}}
],
"filter": [
{"term": {"category": "electronics"}},
{"range": {"price": {"gte": 100, "lte": 300}}}
],
"should": [
{"term": {"in_stock": true}}
]
}
},
"sort": [
{"price": {"order": "asc"}}
],
"highlight": {
"fields": {"description": {}}
}
}Deep Pagination (search_after)
from + size exceeds 10,000, you must use search_after. Pass the last sort value of the previous result to fetch the next page.
{
"query": {"match_all": {}},
"sort": [
{"_meta.created_at": {"order": "desc"}},
{"_id": {"order": "asc"}}
],
"size": 100,
"search_after": ["2025-01-01T00:00:00Z", "product-500"]
}Note: search_after When using it, you must specify sort, and it is recommended to include _id as a tie-breaker.
Search Response Example
{
"total": 42,
"hits": [
{
"id": "product-001",
"score": 1.5,
"data": {
"title": "Wireless Headphones",
"price": 199.99,
...
},
"highlight": {
"description": ["High-quality <em>wireless</em> <em>headphones</em>..."]
}
}
]
}Vector Search (k-NN)
Pro+Semantic similarity search using embedding vectors. You can store and search text/image embeddings generated by AI models.
Schema with a Vector Field
{
"name": "articles",
"schema_fields": {
"title": {"type": "text"},
"content": {"type": "text"},
"embedding": {
"type": "knn_vector",
"dimension": 1536,
"space_type": "cosinesimil"
}
}
}Vector Search Request
curl -X POST https://api.core.today/v1/databases/{database_uid}/search/vector \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"field": "embedding",
"vector": [0.1, 0.2, 0.3, ...],
"k": 10,
"filter": {
"term": {"category": "technology"}
},
"_source": ["title", "content"]
}'Auto-embedding (no vector management)
Declare `embed` on a knn_vector field and the vector is computed from `source_field` on every write that carries it — through the LLM API with your API key, billed like a direct embeddings call (text-embedding-3-small ≈ 0.04 credits per 1K tokens). Then search with `text` instead of `vector`. A patch that does not touch the source field makes no embedding call; a document that carries the vector explicitly is stored as-is.
// 1. schema: the vector is derived from "body"
"schema_fields": {
"body": {"type": "text"},
"vec": {"type": "knn_vector", "dimension": 1536,
"embed": {"source_field": "body", "model": "openai/text-embedding-3-small"}}
}
// 2. write documents with text only
POST /v1/databases/{database_uid}/documents
{"id": "faq-1", "data": {"body": "How do I reset my password?"}}
// 3. search by text
POST /v1/databases/{database_uid}/search/vector
{"field": "vec", "text": "forgot password", "k": 5}`dimension` must match the model: openai/text-embedding-3-small → 1536, openai/text-embedding-3-large → 3072. If the embedding call fails the write is aborted with 502 `embedding_failed` (nothing stored). Console writes use one of the workspace's active API keys.
Embedding Tips
Prefer `embed` above; if you compute vectors yourself, OpenAI's text-embedding-3-small (1536 dims) via the LLM API is the reference model — store the vector in the knn_vector field.
Aggregations
Provides aggregation features for data analysis.
curl -X POST https://api.core.today/v1/databases/{database_uid}/aggregate \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"query": {"match_all": {}},
"aggregations": {
"by_category": {
"terms": {"field": "category", "size": 10}
},
"avg_price": {
"avg": {"field": "price"}
},
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{"to": 100},
{"from": 100, "to": 200},
{"from": 200}
]
}
}
}
}'Aggregation Response Example
{
"total": 1000,
"aggregations": {
"by_category": {
"buckets": [
{"key": "electronics", "doc_count": 350},
{"key": "clothing", "doc_count": 280},
...
]
},
"avg_price": {"value": 149.99},
"price_ranges": {
"buckets": [
{"key": "*-100.0", "doc_count": 200},
{"key": "100.0-200.0", "doc_count": 500},
{"key": "200.0-*", "doc_count": 300}
]
}
}
}Database Management
List Databases
curl https://api.core.today/v1/databases \
-H "X-API-Key: cdt_your_api_key"Update Name/Description
Only the display name and description can be updated. Schema fields cannot be changed with this API — use the add-fields API below to add fields.
curl -X PATCH https://api.core.today/v1/databases/{database_uid} \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"display_name": "New Display Name",
"description": "Updated description"
}'Add Fields
You can add new fields to an existing database. The type of existing fields cannot be changed.
curl -X POST https://api.core.today/v1/databases/{database_uid}/fields \
-H "X-API-Key: cdt_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"fields": {
"rating": {"type": "float"},
"reviews_count": {"type": "integer"}
}
}'Get Stats
curl https://api.core.today/v1/databases/{database_uid}/stats \
-H "X-API-Key: cdt_your_api_key"Delete Database
Warning: Deleting a database is irreversible. All documents are permanently deleted.
Deleting and recreating is the only way to fix an incorrect schema. Be sure to export any documents you need before deleting. You can also delete a database from the console's Databases page.
curl -X DELETE https://api.core.today/v1/databases/{database_uid} \
-H "X-API-Key: cdt_your_api_key"Field Value Autocomplete (Suggest)
keyword type field values via autocomplete.
curl "https://api.core.today/v1/databases/{database_uid}/suggest?field=category&prefix=ele&limit=10" \
-H "X-API-Key: cdt_your_api_key"
# Response: {"suggestions": ["electronics", "electric-vehicles", ...]}Free: No credits are deducted.
Saved Queries
You can save and manage frequently used search queries. All free.
docs.databases.mgmt.queriesCodeSDK Examples (@coredot/aiapi · aiapi)
JavaScript / TypeScript
import { AIAPI } from "@coredot/aiapi";
const client = new AIAPI({ apiKey: "cdt_your_api_key" });
// Create a database (Korean analyzer for text fields)
const db = await client.databases.create({
name: "products",
language: "ko",
schema_fields: { title: { type: "text" }, price: { type: "float" }, status: { type: "keyword" } },
});
// Create-or-merge — no "PATCH, then POST on 404"
const doc = await client.databases.upsert(db.uid, "product-001", { title: "무선 헤드폰", price: 99.99 });
console.log(doc.result); // "created" | "updated"
// Search (text fields are sortable; writes are searchable within ~1s)
const results = await client.databases.search(db.uid, {
query: { match: { title: "헤드폰" } },
sort: [{ title: "asc" }],
size: 10,
});
console.log(results.hits);Python
from aiapi import AIAPI
client = AIAPI(api_key="cdt_your_api_key")
# Create a database (Korean analyzer for text fields)
db = client.databases.create(
"products",
{"title": {"type": "text"}, "price": {"type": "float"}, "status": {"type": "keyword"}},
language="ko",
)
# Create-or-merge — no "PATCH, then POST on 404"
doc = client.databases.upsert(db.uid, "product-001", {"title": "무선 헤드폰", "price": 99.99})
print(doc.result) # "created" | "updated"
# Search (text fields are sortable; writes are searchable within ~1s)
results = client.databases.search(db.uid, {"match": {"title": "헤드폰"}}, sort=[{"title": "asc"}], size=10)
print(results.hits)Error Responses
When a request fails, the following error responses are returned. If an operation that deducted credits fails, the credits are automatically refunded.
402 — Insufficient Credits
{
"detail": "Insufficient credits. Required: 0.50, Available: 0.12"
}404 — Database or Document Not Found
{
"detail": "Database not found"
}
// or
{
"detail": "Document not found"
}400 — Validation Failed
{
"detail": "Database with name 'products' already exists"
}
// or
{
"detail": "Invalid query: Query type 'script' is not allowed"
}
// or
{
"detail": "Pagination limit exceeded: from (9990) + size (20) = 10010 exceeds maximum of 10000. Use search_after for deep pagination."
}403 — Plan Limit
{
"detail": "Vector search is not available in your plan"
}
// or
{
"detail": "Maximum number of databases (1) reached for your plan"
}Reseller: Per-customer Data Isolation
When a team with reseller mode enabled sends an X-Customer-Id header to the public document endpoints, all document reads/writes/searches within a single database are automatically isolated to that end-customer. You do not need to create a separate DB per customer.
How It Works
- Writes: On create/bulk-create/update, the document is automatically tagged with that customer.
- Reads & search: Get/search/count/vector search are automatically filtered to that customer's documents.
- Cross-access blocked: Getting/deleting another customer's document returns
404(existence is not revealed either). - Without the header: for reseller teams the request is rejected with 400 (customer_scope_required) — isolation never silently turns off. To manage the entire dataset directly, send an explicit X-Customer-Scope: all header. Non-reseller teams are unaffected.
Applicable endpoints: /databases/{uid}/documents (and /_bulk), /documents/{doc_id}, /documents/_count, /search, /search/vector.
Isolation guarantee: One customer's data is never exposed to another. If a non-reseller team sends this header, it is ignored.
Reserved field prefix _ — differs from the naming rule
Document field names starting with an underscore (_) are system-reserved and are automatically stripped from the document body (so internal fields such as the customer tag cannot be spoofed). This is a database name prefix rule (forbidding aiapi_, db_system_, _ as the start of a DB name) described earlier, but a different rule. The former applies to a document's field keys, the latter to the DB's name.
Example — same DB, two customers' document sets isolated from each other
docs.databases.reseller.exampleCodeBest Practices
- Schema design: Use the
keywordtype for fields frequently used in search, and thetexttype for fields that need full-text search. - Bulk operations: When processing multiple documents, use the
/_bulkendpoints. - Pagination:
from + sizecan go up to 10,000. Beyond that, usesearch_aftertogether withsort. - Filter vs query: Put exact value matching in
filter, and searches that need scoring inquery. - Vector search: Set the vector dimension to match your embedding model; using it together with filters is more efficient.