Skip to main content
Core.Today

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.

OperationEndpointCredits
Create documentPOST /documents0.1
Get documentGET /documents/{id}0.05
Update documentPUT, PATCH /documents/{id}0.1
Delete documentDELETE /documents/{id}0.05
Bulk createPOST /documents/_bulk5.0 / 100 docs
Bulk deletePOST /documents/_bulk_delete0.05 / doc
Bulk updatePOST /documents/_bulk_update0.1 / doc
Full-text searchPOST /search0.5
AggregationPOST /aggregate1.0
Vector searchPOST /search/vector2.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.

ItemFreeProTeamEnterprise
Max databases1520100
Max documents per DB10,000500,0005,000,00020,000,000
Max storage100 MB2 GB10 GB50 GB
Max document size100 KB1 MB10 MB25 MB
Max bulk operation size1001,00010,000100,000
Vector search-1,536 dims2,048 dims4,096 dims
Requests per minute (read / search / write, per team)300 / 60 / 1201,500 / 300 / 6005,000 / 1,000 / 2,00015,000 / 3,000 / 6,000
1

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

  1. If you have documents to keep, export them first (e.g. via the search API). Deleting a database deletes all of its documents.
  2. Create a new database with the correct schema and re-insert the exported documents using the bulk create API (_bulk).
  3. 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

TypeDescriptionExample
textFull-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..."
keywordString for exact matching (filters, aggregations)"electronics"
integerInteger42
long64-bit integer9223372036854775807
float32-bit floating point19.99
double64-bit floating point3.141592653589793
booleantrue/falsetrue
dateISO 8601 date"2024-12-26T10:00:00Z"
objectNested object (indexing can be turned on/off){"key": "value"}
nestedArray of independently queryable objects[{"name": "A"}, ...]
knn_vectorEmbedding for vector search (Pro+)[0.1, 0.2, ...]
geo_pointLocation {"lat", "lon"} — geo_distance queries, where $near{"lat": 37.5, "lon": 127.0}
ipIPv4/IPv6 address — term queries accept CIDR ("10.0.0.0/8")"10.0.0.5"
date_rangeDate 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"
}
2

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.createCode

Create-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.updateCode

Upsert: 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.excludeCode
3

Search

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>..."]
      }
    }
  ]
}
4

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.

5

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}
      ]
    }
  }
}
6

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.queriesCode

SDK 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"
}
R

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.exampleCode

Best Practices

  • Schema design: Use the keyword type for fields frequently used in search, and the text type for fields that need full-text search.
  • Bulk operations: When processing multiple documents, use the /_bulk endpoints.
  • Pagination: from + size can go up to 10,000. Beyond that, use search_after together with sort.
  • Filter vs query: Put exact value matching in filter, and searches that need scoring in query.
  • Vector search: Set the vector dimension to match your embedding model; using it together with filters is more efficient.