Databases
OpenSearch 기반의 고성능 문서 데이터베이스입니다. 스키마 정의, CRUD, 전문 검색, 벡터 검색을 지원합니다.
주요 기능
- 스키마 정의: 필드 타입, 인덱싱 옵션, 벡터 필드 설정
- CRUD 작업: 단일/대량 문서 생성, 조회, 수정, 삭제
- 전문 검색: OpenSearch Query DSL 지원 (match, term, bool, range)
- 벡터 검색: k-NN 기반 시맨틱 검색 (Pro 플랜 이상)
- 집계: terms, date_histogram, 통계 집계
크레딧 비용
데이터베이스 작업은 유형에 따라 크레딧이 차감됩니다. 작업 실패 시 크레딧은 자동 환불됩니다.
| 작업 | 엔드포인트 | 크레딧 |
|---|---|---|
| 문서 생성 | POST /documents | 0.1 |
| 문서 조회 | GET /documents/{id} | 0.05 |
| 문서 수정 | PUT, PATCH /documents/{id} | 0.1 |
| 문서 삭제 | DELETE /documents/{id} | 0.05 |
| 대량 생성 | POST /documents/_bulk | 5.0 / 100건 |
| 대량 삭제 | POST /documents/_bulk_delete | 0.05 / 건 |
| 대량 수정 | POST /documents/_bulk_update | 0.1 / 건 |
| 전문 검색 | POST /search | 0.5 |
| 집계 | POST /aggregate | 1.0 |
| 벡터 검색 | POST /search/vector | 2.0 |
무료: 데이터베이스 생성/삭제, 목록 조회, 스키마 변경, 통계 조회, 문서 목록 조회, 문서 수 조회(_count)는 크레딧이 차감되지 않습니다.
플랜별 제한
데이터베이스 리소스 한도는 구독 플랜에 따라 다릅니다. 표의 한도는 공용 클러스터 기준이며, 그 이상은 전용 노드로 제공합니다(문의). 클러스터 여유가 부족하면 쓰기가 일시적으로 503 database_capacity로 거부될 수 있습니다.
| 항목 | Free | Pro | Team | Enterprise |
|---|---|---|---|---|
| 최대 데이터베이스 수 | 1 | 5 | 20 | 100 |
| DB당 최대 문서 수 | 10,000 | 500,000 | 5,000,000 | 20,000,000 |
| 최대 스토리지 | 100 MB | 2 GB | 10 GB | 50 GB |
| 최대 문서 크기 | 100 KB | 1 MB | 10 MB | 25 MB |
| 벌크 작업 최대 건수 | 100 | 1,000 | 10,000 | 100,000 |
| 벡터 검색 | - | 1,536차원 | 2,048차원 | 4,096차원 |
| 분당 요청 수 (조회 / 검색 / 쓰기, 팀 단위) | 300 / 60 / 120 | 1,500 / 300 / 600 | 5,000 / 1,000 / 2,000 | 15,000 / 3,000 / 6,000 |
데이터베이스 생성
스키마와 함께 새 데이터베이스를 생성합니다. 데이터베이스 이름은 팀 내에서 고유해야 합니다.
이름 규칙
- 소문자 영문으로 시작해야 합니다
- 소문자 영문, 숫자, 하이픈(
-), 언더스코어(_)만 사용 가능 - 3~50자 이내
aiapi_,db_system_,_로 시작할 수 없음 (예약 접두사)
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"}
}
}'응답 예시
{
"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"
}스키마 수정/삭제는 어떻게 하나요?
스키마를 잘못 정의했을 때 자주 받는 질문입니다. 검색 인덱스(OpenSearch 매핑) 특성상 이미 생성된 필드는 변경할 수 없으며, 가능한 작업과 불가능한 작업은 아래와 같습니다.
- ✅ 새 필드 추가: 필드 추가 API(POST /databases/{uid}/fields)로 언제든 가능합니다 (무료). 필드를 빠뜨린 경우라면 삭제 없이 이 방법을 사용하세요.
- ✅ 이름/설명 수정: 표시 이름(display_name)과 설명(description)은 PATCH /databases/{uid}로 수정할 수 있습니다 (무료).
- ❌ 기존 필드 타입 변경: 이미 정의된 필드의 타입(예: text → keyword)은 변경할 수 없습니다.
- ❌ 기존 필드 삭제: 스키마에서 개별 필드만 제거하는 것은 지원되지 않습니다.
스키마를 잘못 만들었다면: 삭제 후 재생성
- 보관할 문서가 있다면 검색 API 등으로 먼저 백업(export)하세요. 데이터베이스를 삭제하면 모든 문서가 함께 삭제됩니다.
- 올바른 스키마로 새 데이터베이스를 생성하고, 백업한 문서를 대량 생성 API(_bulk)로 다시 넣으세요.
- 새 데이터베이스가 정상 동작하는 것을 확인한 뒤 기존 데이터베이스를 삭제하세요. 삭제는 되돌릴 수 없습니다.
데이터베이스 삭제는 콘솔의 팀 워크스페이스 → Databases 페이지에서도 할 수 있습니다. 필드 추가와 이름/설명 수정은 현재 API로만 지원됩니다.
지원 필드 타입
| 타입 | 설명 | 예시 |
|---|---|---|
text | 전문 검색이 가능한 텍스트. 정렬·집계용 .keyword 서브필드가 자동으로 붙고(정렬/집계에 필드명을 그대로 쓰면 자동 변환), 형태소 분석기는 language(ko=nori · en · ja · zh) 또는 analyzer로 지정 | "Product description..." |
keyword | 정확한 매칭용 문자열 (필터, 집계) | "electronics" |
integer | 정수 | 42 |
long | 64비트 정수 | 9223372036854775807 |
float | 32비트 부동 소수점 | 19.99 |
double | 64비트 부동 소수점 | 3.141592653589793 |
boolean | true/false | true |
date | ISO 8601 날짜 | "2024-12-26T10:00:00Z" |
object | 중첩 객체 (인덱싱 on/off 가능) | {"key": "value"} |
nested | 독립적으로 쿼리 가능한 객체 배열 | [{"name": "A"}, ...] |
knn_vector | 벡터 검색용 임베딩 (Pro+) | [0.1, 0.2, ...] |
geo_point | 위치 {"lat", "lon"} — geo_distance 질의, where $near | {"lat": 37.5, "lon": 127.0} |
ip | IPv4/IPv6 주소 — term 질의에 CIDR("10.0.0.0/8") 사용 가능 | "10.0.0.5" |
date_range | 날짜 구간 {"gte", "lte"} — range 질의는 교차 여부로 매칭 | {"gte": "2026-01-01", "lte": "2026-01-31"} |
언어(형태소 분석기) 설정
데이터베이스 생성 시 language를 주면 모든 text 필드의 기본 분석기가 됩니다(ko → nori). 필드별 language/analyzer가 있으면 그 값이 우선합니다. 기존 필드의 분석기는 바꿀 수 없고(재색인 필요) 이후 추가되는 필드부터 적용됩니다.
{
"name": "articles",
"language": "ko",
"schema_fields": {
"title": { "type": "text" },
"summary": { "type": "text", "language": "en" },
"status": { "type": "keyword" }
}
}동의어와 refresh 간격
`synonyms`는 Solr 형식의 검색 시점 규칙입니다 — "usa, united states"(동등) 또는 "phone => mobile"(단방향). 언어별(ko/en/ja/zh) 분석기를 통해 모든 text 필드에 적용되므로 문서를 다시 색인하지 않습니다. 관리형 클러스터는 인덱스를 닫을 수 없어 나중에 바꾸려면 POST /reindex(스키마 진화)를 거칩니다. `refresh_interval`(1s … 60s, 기본 1s)은 검색 반영 상한으로, 쓰기가 많은 DB에서 올리면 색인 처리량이 좋아지며 PATCH로 즉시 적용됩니다.
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"}}}}벡터 필드 설정
벡터 검색을 위한 필드는 dimension과 space_type을 지정해야 합니다:
"embedding": {
"type": "knn_vector",
"dimension": 1536,
"space_type": "cosinesimil"
}문서 CRUD
자동 메타데이터 (_meta)
모든 문서에는 _meta 필드가 자동으로 추가됩니다:
_meta.created_at— 문서 생성 시각 (ISO 8601)_meta.updated_at— 마지막 수정 시각 (ISO 8601)_meta.created_by_api_key— 생성에 사용된 API 키 UID
문서 생성
docs.databases.crud.createCode생성 전용: 이미 존재하는 id로 POST하면 409(document_already_exists)를 반환하며 덮어쓰지 않습니다. 기존 문서를 바꾸려면 PUT/PATCH(필요 시 ?upsert=true)를 쓰세요. _bulk는 종전대로 같은 id를 덮어씁니다.
대량 문서 생성
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}}
]
}'응답: 성공/실패 개수와 에러 상세 정보가 반환됩니다.
문서 조회
# 특정 문서 조회
curl https://api.core.today/v1/databases/{database_uid}/documents/{doc_id} \
-H "X-API-Key: cdt_your_api_key"
# 특정 필드만 조회 (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"
# 문서 목록 조회 (페이지네이션)
curl "https://api.core.today/v1/databases/{database_uid}/documents?size=20&from=0" \
-H "X-API-Key: cdt_your_api_key"
# 문서 목록 - 특정 필드만 조회
curl "https://api.core.today/v1/databases/{database_uid}/documents?size=20&_source=title,price" \
-H "X-API-Key: cdt_your_api_key"_source: 쉼표로 구분된 필드 목록을 지정하면 해당 필드만 응답에 포함됩니다. 생략하면 모든 필드를 반환합니다.
문서 수정
docs.databases.crud.updateCode업서트: PUT/PATCH는 문서가 없으면 404를 반환합니다. ?upsert=true를 붙이면 없을 때 생성하고 있을 때 수정합니다 — "먼저 PATCH, 404면 POST" 패턴을 대체하며, 생성/수정 여부는 응답의 result 필드로 구분합니다. _bulk_update도 ?upsert=true를 지원합니다.
일관성 모델
ID로 읽기·수정·삭제는 어떤 쓰기 직후에도 즉시 일관됩니다. 검색·목록·_count·집계는 쓰기 후 약 1초 이내에 반영됩니다(eventual). 모든 쓰기 엔드포인트는 ?refresh=false|true|wait_for 를 받으며 기본값은 false입니다. 쓰기 직후 반드시 검색해야 할 때만 refresh=true 를 쓰세요 — 해당 요청의 지연이 최대 약 1초 늘어납니다.
내구성과 복구
응답된 모든 쓰기는 먼저 S3에 저장되고(문서당 객체 하나, 조건부 PUT) 그 다음 색인됩니다. 검색 인덱스는 질의 레이어로, 언제든 S3에서 재구축할 수 있습니다(POST /reindex, 무중단). 일일 감사가 DB마다 S3와 인덱스의 문서 수를 대조해 어긋나면 운영팀에 알립니다. 삭제는 S3와 인덱스 모두에서 제거되며 휴지통이 없으니, 사본이 필요하면 먼저 POST /export 로 내보내세요.
문서 삭제
curl -X DELETE https://api.core.today/v1/databases/{database_uid}/documents/{doc_id} \
-H "X-API-Key: cdt_your_api_key"대량 삭제
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"]
}'크레딧: 0.05 × 문서 수. 존재하지 않는 ID는 에러로 보고됩니다.
대량 수정
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"}}
]
}'크레딧: 0.1 × 문서 수. 부분 수정(PATCH)으로 동작하며 지정된 필드만 업데이트됩니다.
내보내기 / 가져오기 (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"동작·크레딧: 내보내기는 S3 원본 전체를 gzip NDJSON({id, data, _meta} 한 줄에 한 문서)으로 만들어 7일 유효한 서명 URL을 돌려주며 무료입니다(리셀러는 X-Customer-Id로 해당 회원분만). 가져오기는 URL을 서버가 받아(사설망 차단, 최대 50 MB) 파싱한 뒤 문서 수만큼 벌크 생성 요율(100건당 5cr)을 먼저 차감하고 백그라운드로 적재합니다. mode=upsert는 같은 id를 덮어쓰고, mode=create는 기존 id를 항목별 오류로 남깁니다. 진행 상황은 GET …/import 의 done/failed/errors(최대 100건)로 확인하세요. 한 DB에 동시에 한 작업만(409 job_already_running).
스키마 변경 · 재색인 (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"동작: POST /fields는 필드 추가만 가능합니다. 타입 변경·필드 삭제·분석기(language) 변경은 S3 원본에서 새 인덱스를 만드는 재색인으로 처리합니다 — 비동기(202)이며 진행 중에도 조회·검색은 기존 인덱스로 계속 됩니다. 쓰기(생성·수정·삭제·벌크)는 완료까지 409 database_reindexing으로 거부되니 Retry-After 뒤 재시도하세요. 완료되면 레코드가 새 인덱스로 전환되고 옛 인덱스는 삭제됩니다. 실패하면 새 인덱스를 버리고 원래 상태로 복구되며 reindex.error에 원인이 남습니다. 무료.
낙관적 동시성 제어 (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"}}'동작: _meta.version은 저장되지 않는 응답 전용 토큰(<seq_no>.<primary_term>)이며 GET·검색 히트·쓰기 응답 모두에 실립니다. PUT/PATCH/DELETE에 If-Match로 되돌려 보내면 그 버전 이후 문서가 바뀌었을 때 412 precondition_failed로 거부되어(아무것도 쓰지 않음) 읽고-수정-쓰기 경합에서 마지막 쓰기가 앞선 변경을 덮어쓰는 것을 막습니다. 토큰 형식이 잘못되면 400 invalid_if_match(과금 전). upsert 생성과는 함께 쓸 수 없습니다.
변경 이벤트 웹훅
문서가 생성·수정·삭제되면 팀 웹훅으로 document.created / document.updated / document.deleted 이벤트가, 벌크·조건부·TTL 정리로 바뀌면 documents.bulk_created / bulk_updated / bulk_deleted(ids 최대 1,000개 + count, source 필드)가 전송됩니다. 콘솔 › Webhooks에서 구독하세요. 기존 웹훅과 같은 HMAC 서명·재시도·전송 이력이 적용되며, 폴링 대신 이 이벤트로 외부 시스템을 동기화할 수 있습니다.
{
"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 }문서 만료 (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"}'동작: expires_at(절대 시각) 또는 ttl_seconds(상대, 최소 60초)를 주면 _meta.expires_at에 저장되고, 매시간 도는 정리 작업이 만료된 문서를 삭제합니다(S3 원본까지 함께, 삭제 크레딧 없음). 만료 시각이 지나도 다음 정리 전까지는 조회·검색될 수 있으니 필요하면 _meta.expires_at으로 필터하세요. PATCH/PUT에 expires_at: null을 보내면 만료가 해제됩니다.
조건부 대량 삭제 / 수정 (_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"}}'동작·크레딧: 쿼리로 대상 ID를 먼저 선택(검색 0.5cr)한 뒤 벌크 삭제(0.05×건)/벌크 수정(0.1×건)을 수행합니다 — S3 원본 동기화·리셀러 스코프·항목별 결과가 벌크 엔드포인트와 동일합니다. 한 번에 max_docs(기본 1000, 플랜 벌크 한도 이내)까지 처리하고 응답의 matched가 그보다 크면 반복 호출하세요. 크레딧이 부족하면 아무것도 삭제·수정하지 않고 402를 반환합니다.
문서 수 조회
# 전체 문서 수
curl https://api.core.today/v1/databases/{database_uid}/documents/_count \
-H "X-API-Key: cdt_your_api_key"
# 조건부 문서 수 (query 파라미터로 JSON 전달)
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"무료: 크레딧이 차감되지 않습니다. 응답: {"count": 1234}
필드 제외 (_source_excludes)
응답에서 특정 필드를 제외할 수 있습니다. _source 와 함께 사용하면 includes/excludes로 변환됩니다.
docs.databases.crud.excludeCode검색
OpenSearch Query DSL을 사용한 강력한 검색 기능을 제공합니다.
전문 검색 (Full-text)
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: 응답에 포함할 필드 목록입니다. 생략하면 모든 필드를 반환합니다.
단순 필터 (where)
대부분의 조회는 Query DSL이 필요 없습니다. `where`는 평범한 JSON 객체로, 서버가 필터(점수 없음)로 컴파일하며 `query`/`filter`와 함께 주면 AND로 결합됩니다. 검색·목록(`?where=<json>`)·벡터 검색·집계·by-query 엔드포인트 모두에서 동작합니다.
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"}}}연산자: 동등 `{"status": "active"}` · `$ne` · `$in`/`$nin` · `$gt`/`$gte`/`$lt`/`$lte` · `$exists` · `$prefix` · `$contains`(text 필드는 구절 일치, keyword는 부분 문자열) · `$match`(분석된 전문 검색) · `$and`/`$or`/`$not`. text 필드의 정확 비교는 `.keyword` 서브필드로 자동 변환됩니다. `null`은 "값 없음"입니다. 잘못된 필터는 과금 전에 400 `invalid_where`로 거부됩니다.
복합 검색 (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 가 10,000을 초과하면 search_after를 사용해야 합니다. 이전 결과의 마지막 sort 값을 전달하여 다음 페이지를 조회합니다.
{
"query": {"match_all": {}},
"sort": [
{"_meta.created_at": {"order": "desc"}},
{"_id": {"order": "asc"}}
],
"size": 100,
"search_after": ["2025-01-01T00:00:00Z", "product-500"]
}주의: search_after 사용 시 반드시 sort를 지정해야 하며, tie-breaker로 _id 를 포함하는 것을 권장합니다.
검색 응답 예시
{
"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>..."]
}
}
]
}벡터 검색 (k-NN)
Pro+임베딩 벡터를 사용한 시맨틱 유사도 검색입니다. AI 모델로 생성한 텍스트/이미지 임베딩을 저장하고 검색할 수 있습니다.
벡터 필드가 있는 스키마
{
"name": "articles",
"schema_fields": {
"title": {"type": "text"},
"content": {"type": "text"},
"embedding": {
"type": "knn_vector",
"dimension": 1536,
"space_type": "cosinesimil"
}
}
}벡터 검색 요청
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"]
}'자동 임베딩 (벡터를 직접 관리하지 않아도 됩니다)
knn_vector 필드에 `embed`를 선언하면 `source_field`를 담은 모든 쓰기에서 벡터가 자동 계산됩니다 — LLM API를 내 API 키로 호출하므로 직접 임베딩 호출과 같은 과금(text-embedding-3-small 1K 토큰당 약 0.04 크레딧)입니다. 검색은 `vector` 대신 `text`를 보내면 됩니다. 소스 필드를 건드리지 않는 patch는 임베딩 호출이 없고, 벡터를 직접 담아 보낸 문서는 그대로 저장됩니다.
// 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`은 모델과 일치해야 합니다: openai/text-embedding-3-small → 1536, openai/text-embedding-3-large → 3072. 임베딩 호출이 실패하면 쓰기는 502 `embedding_failed`로 중단됩니다(아무것도 저장되지 않음). 콘솔 쓰기는 워크스페이스의 활성 API 키 중 하나를 사용합니다.
임베딩 생성 팁
위의 `embed`를 권장합니다. 벡터를 직접 계산한다면 OpenAI의 text-embedding-3-small (1536차원)이 기준 모델입니다 — LLM API로 임베딩을 생성해 knn_vector 필드에 저장하세요.
집계 (Aggregations)
데이터 분석을 위한 집계 기능을 제공합니다.
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}
]
}
}
}
}'집계 응답 예시
{
"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}
]
}
}
}데이터베이스 관리
데이터베이스 목록
curl https://api.core.today/v1/databases \
-H "X-API-Key: cdt_your_api_key"이름/설명 수정
표시 이름(display_name)과 설명(description)만 수정할 수 있습니다. 스키마 필드는 이 API로 변경할 수 없으며, 필드 추가는 아래 필드 추가 API를 사용하세요.
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"
}'필드 추가
기존 데이터베이스에 새 필드를 추가할 수 있습니다. 기존 필드의 타입은 변경할 수 없습니다.
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"}
}
}'통계 조회
curl https://api.core.today/v1/databases/{database_uid}/stats \
-H "X-API-Key: cdt_your_api_key"데이터베이스 삭제
주의: 데이터베이스 삭제는 되돌릴 수 없습니다. 모든 문서가 영구 삭제됩니다.
잘못 만든 스키마를 바로잡는 방법은 삭제 후 재생성뿐입니다. 필요한 문서는 삭제 전에 반드시 백업하세요. 콘솔의 Databases 페이지에서도 삭제할 수 있습니다.
curl -X DELETE https://api.core.today/v1/databases/{database_uid} \
-H "X-API-Key: cdt_your_api_key"필드값 자동완성 (Suggest)
keyword 타입 필드의 값을 자동완성으로 조회합니다.
curl "https://api.core.today/v1/databases/{database_uid}/suggest?field=category&prefix=ele&limit=10" \
-H "X-API-Key: cdt_your_api_key"
# 응답: {"suggestions": ["electronics", "electric-vehicles", ...]}무료: 크레딧이 차감되지 않습니다.
쿼리 저장
자주 사용하는 검색 쿼리를 저장하고 관리할 수 있습니다. 모두 무료입니다.
docs.databases.mgmt.queriesCodeSDK 예제 (@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)에러 응답
요청 실패 시 다음과 같은 에러 응답이 반환됩니다. 크레딧이 차감된 작업이 실패하면 자동으로 환불됩니다.
402 — 크레딧 부족
{
"detail": "Insufficient credits. Required: 0.50, Available: 0.12"
}404 — 데이터베이스 또는 문서 없음
{
"detail": "Database not found"
}
// 또는
{
"detail": "Document not found"
}400 — 유효성 검증 실패
{
"detail": "Database with name 'products' already exists"
}
// 또는
{
"detail": "Invalid query: Query type 'script' is not allowed"
}
// 또는
{
"detail": "Pagination limit exceeded: from (9990) + size (20) = 10010 exceeds maximum of 10000. Use search_after for deep pagination."
}403 — 플랜 제한
{
"detail": "Vector search is not available in your plan"
}
// 또는
{
"detail": "Maximum number of databases (1) reached for your plan"
}리셀러: 고객별 데이터 격리
리셀러 모드가 활성화된 팀이 공개 문서 엔드포인트에 X-Customer-Id 헤더를 함께 보내면, 하나의 데이터베이스 안에서 모든 문서 읽기/쓰기/검색이 해당 최종 고객(end-customer)에게 자동으로 격리됩니다. 고객마다 별도의 DB를 만들 필요가 없습니다.
동작 방식
- 쓰기: 생성/대량 생성/수정 시 문서에 해당 고객 태그가 자동으로 부착됩니다.
- 읽기·검색: 조회/검색/카운트/벡터 검색이 해당 고객의 문서로 자동 필터링됩니다.
- 교차 접근 차단: 다른 고객의 문서를 get/delete 하면
404를 반환합니다(존재 여부도 노출되지 않음). - 헤더가 없으면: 리셀러 팀에서는 400 (customer_scope_required)으로 거부됩니다 — 격리가 조용히 풀리지 않습니다. 전체 데이터셋을 직접 관리하려면 X-Customer-Scope: all 헤더를 명시적으로 보내세요. 비리셀러 팀은 영향 없습니다.
적용 엔드포인트: /databases/{uid}/documents (및 /_bulk), /documents/{doc_id}, /documents/_count, /search, /search/vector.
격리 보장: 한 고객의 데이터는 다른 고객에게 절대 노출되지 않습니다. 리셀러 모드가 아닌 팀이 이 헤더를 보내면 무시됩니다.
예약 필드 접두사 _ — 이름 규칙과 다릅니다
문서 필드 이름 중 밑줄(_)로 시작하는 것은 시스템 예약이며, 문서 본문에서 자동으로 제거됩니다(고객 태그 등 내부 필드를 스푸핑할 수 없도록). 이는 앞서 설명한 데이터베이스 이름 접두사 규칙(aiapi_, db_system_, _ 로 시작하는 DB 이름을 금지)과는 다른 규칙입니다. 전자는 문서의 필드 키, 후자는 DB의 이름에 적용됩니다.
예시 — 같은 DB, 두 고객의 문서 집합이 서로 격리됨
docs.databases.reseller.exampleCodeBest Practices
- 스키마 설계: 검색에 자주 사용되는 필드는
keyword타입으로, 전문 검색이 필요한 필드는text타입으로 지정하세요. - 대량 작업: 여러 문서를 처리할 때는
/_bulk엔드포인트를 사용하세요. - 페이지네이션:
from + size는 최대 10,000까지 가능합니다. 그 이상은search_after와sort를 함께 사용하세요. - 필터 vs 쿼리: 정확한 값 매칭은
filter에, 점수 계산이 필요한 검색은query에 작성하세요. - 벡터 검색: 벡터 차원은 임베딩 모델에 맞게 설정하고, 필터와 함께 사용하면 더 효율적입니다.