dots
Dots
Use this skill when the user wants to read or mutate graph data through the Dots HTTP API.
Workflow
1. Config resolution
Check for dots.json in CWD.
Default base URL is fixed to https://dots.tallpizza.com.
If missing — ask the user for API key (sk_... format). Only ask for a base URL if the user explicitly wants to use a different server. Then discover available targets:
# List workspaces accessible with this key
curl -s -H "Authorization: Bearer $API_KEY" "$BASE_URL/api/workspaces"
# List spaces in a workspace
curl -s -H "Authorization: Bearer $API_KEY" "$BASE_URL/api/workspaces/$WS_SLUG/spaces"
Present the available workspaces and spaces. Let the user pick one (or match the one mentioned in their prompt). Then write dots.json.
If the user did not explicitly override the base URL, omit base_url and rely on the default:
{
"workspace_slug": "acme",
"space_slug": "demo",
"api_key": "sk_..."
}
If the user explicitly chose a different server, include base_url in dots.json.
Add dots.json to .gitignore if not already present (contains API key).
If exists — read it and use the stored values.
2. Target resolution
- Default workspace/space come from
dots.json. - If the user names a different space in their prompt (e.g., "demo 스페이스에 넣어줘"), use that slug for the request. Update
dots.jsonwith the new space so it becomes the default for next time. - If the user explicitly names a different base URL, use it and persist it to
dots.json. - If the user names an ambiguous or unknown space, fetch the list via
GET /api/workspaces/:ws/spacesto resolve and confirm.
3. Execute
Make API calls using curl via Bash, reading credentials from dots.json.
4. Confirm
After mutations, fetch the graph snapshot or the specific node to confirm the result. There is no public GET /edges/:edge_id route, so confirm edge writes via the graph snapshot. Show a brief summary including affected IDs and the active space.
Rules
- Credentials come from
dots.jsonin CWD. - Only persist
base_urltodots.jsonwhen the user explicitly selected a non-default server. - Auth:
Authorization: Bearer sk_{public_id}_{secret}on every request. - Never invent endpoints outside this spec.
- Do not expose a live API key unless the user explicitly asks.
- Re-read
dots.jsonbefore multi-step or destructive work if the target may have changed.
Scopes
view: graph get, node get, queryupdate: all ofview+ node/edge CRUD, related node creation
If the requested action conflicts with scope, say so plainly and stop.
ID Format
ULID with 3-letter prefix: nod_, edg_, evt_
Pattern: ^[a-z]{3}_[0-9A-HJKMNP-TV-Z]{26}$
Endpoints
Base path: /api/workspaces/:workspace_slug/spaces/:space_slug (abbreviated …/:sp).
Discovery
GET /api/workspaces
- 설명: 현재 API key로 접근 가능한 workspace 목록 조회
- 사용: 처음
dots.json을 만들 때 workspace를 고를 때 사용 - 파라미터: 없음
GET /api/workspaces/:ws/spaces
- 설명: 특정 workspace 안의 space 목록 조회
- 사용:
workspace_slug를 정한 뒤space_slug를 고를 때 사용 - 파라미터:
ws: workspace slug
Read APIs
GET …/:sp/graph
- 설명: 현재 space의 전체 그래프 스냅샷 조회
- 사용: 전체 노드/엣지를 한 번에 가져오고 싶을 때 사용
- 파라미터: 없음
- 응답:
{ nodes, edges }
POST …/:sp/query
- 설명: 조건에 맞는 노드를 찾고 필요하면 주변 그래프로 확장해서 조회
- 사용:
- 빈 body면 전체 그래프처럼 조회
where만 보내면 조건 검색where+expand면 조건에 맞는 노드 주변 서브그래프 조회
- 파라미터:
where?: 조회 시작 조건 배열field:node_id|kind|title|created_at|updated_atop:eq|in|contains|gt|ltvalue: scalar 또는 scalar 배열
expand?: 확장 규칙 배열dir:in|out|bothmax_depth:1|2|3
- 응답:
{ nodes, edges }
{
"where": [{ "field": "kind", "op": "eq", "value": "concept" }],
"expand": [{ "dir": "out", "max_depth": 2 }]
}
GET …/:sp/nodes/:node_id
- 설명: 단일 노드 상세 조회
- 사용: 특정 노드 하나의 최신 상태를 확인할 때 사용
- 파라미터:
node_id: 조회할 노드 ID
- 응답:
GNode
POST …/:sp/nodes/:node_id/related-words
- 설명: 기준 노드에서 관련 단어 후보를 생성하거나, 바로 노드로 생성
- 사용:
create_nodes=false: 미리보기만 조회create_nodes=true: 관련 단어를 노드/엣지로 생성
- 파라미터:
node_id: 기준 노드 IDcount?: 생성할 항목 수create_nodes:false면 preview,true면 생성
- 응답:
- preview:
{ created: false, items, nodes: [], edges: [] } - create:
{ created: true, items, nodes, edges }
- preview:
Write APIs
POST …/:sp/nodes
- 설명: 노드 생성
- 사용: 새 노드를 추가할 때 사용
- 파라미터:
kind: 노드 타입title: 노드 제목props?: 추가 속성
- 응답:
GNode
PATCH …/:sp/nodes/:node_id
- 설명: 노드 수정
- 사용: 노드의
kind,title,props를 부분 수정할 때 사용 - 파라미터:
node_id: 수정할 노드 IDkind?,title?,props?
- 응답:
GNode
DELETE …/:sp/nodes/:node_id
- 설명: 노드 삭제
- 사용: 특정 노드와 그 incident edge를 제거할 때 사용
- 파라미터:
node_id: 삭제할 노드 ID
- 응답:
204 No Content
POST …/:sp/edges
- 설명: 엣지 생성
- 사용: 기존 두 노드를 연결할 때 사용
- 파라미터:
kind: 엣지 타입from_node_id: 시작 노드 IDto_node_id: 도착 노드 IDprops?: 추가 속성
- 응답:
GEdge
PATCH …/:sp/edges/:edge_id
- 설명: 엣지 수정
- 사용: 엣지의
kind또는props를 수정할 때 사용 - 파라미터:
edge_id: 수정할 엣지 IDkind?,props?
- 응답:
GEdge
DELETE …/:sp/edges/:edge_id
- 설명: 엣지 삭제
- 사용: 특정 연결 하나만 제거할 때 사용
- 파라미터:
edge_id: 삭제할 엣지 ID
- 응답:
204 No Content
POST …/:sp/nodes/:node_id/related-nodes
- 설명: 기준 노드 하나에 연결될 여러 노드를 한 번에 생성
- 사용: 하나의 컨텍스트 노드에 관련 노드들을 묶어서 추가할 때 사용
- 파라미터:
node_id: 기준 노드 IDedge_kind: 생성할 연결의 kinddirection?:out|innodes: 생성할 노드 배열kind: 생성할 노드 타입title: 생성할 노드 제목props?: 생성 노드 propsedge_props?: 기준 노드와 연결되는 edge props
- 응답:
{ root_node_id, nodes, edges }
{
"edge_kind": "related_to",
"direction": "out",
"nodes": [
{
"kind": "concept",
"title": "Graph database",
"props": { "source": "ai" },
"edge_props": { "weight": 1 }
}
]
}
Props Rules
- keys: lowercase
^[a-z][a-z0-9_]*$ - values:
string | number | boolean | null | Array<Scalar> - reserved:
node_id,edge_id,kind,title,from_node_id,to_node_id,created_at,updated_at,created_by,id,space_id
Types
type GNode = {
node_id: string; space_id: string; kind: string; title: string; created_by: string
props: Record<string, GraphValue>; created_at: string; updated_at: string
}
type GEdge = {
edge_id: string; space_id: string; kind: string; from_node_id: string; to_node_id: string
created_by: string; props: Record<string, GraphValue>; created_at: string; updated_at: string
}
type GraphValue = string | number | boolean | null | Array<string | number | boolean | null>
Query Budget
expand.max_depth: 3- request body: max 256 KB
- response: max 1 MB
- query timeout: 3 000 ms
- max nodes in response: 500
- max edges in response: 1 000
Mutation Rules
- Create nodes before dependent edges.
- Use PATCH for partial updates.
- Edge PATCH cannot change
from_node_idorto_node_id— delete and recreate to re-link. - Delete only the exact resource the user asked to remove.
- After writes, confirm the affected IDs and active space.
- Prefer
POST …/:sp/nodes/:node_id/related-nodeswhen adding several nodes around one existing node.
Error Codes
| Code | Meaning |
|---|---|
AUTH_REQUIRED |
No credentials provided |
AUTH_INVALID |
Invalid or expired key |
FORBIDDEN |
Scope too weak for the action |
RATE_LIMITED |
Too many requests |
INVALID_ID |
Malformed node/edge/event ID |
INVALID_KEY |
Malformed API key |
INVALID_GRAPH_PROPERTY |
Reserved property name in props |
BUDGET_EXCEEDED |
Query budget limit hit |
QUERY_BODY_TOO_LARGE |
Request body exceeds 256 KB |
QUERY_DEPTH_EXCEEDED |
Expand depth > 3 |
QUERY_LIMIT_EXCEEDED |
Legacy query limit exceeded |
QUERY_RESULT_EXCEEDED |
Response exceeds node/edge caps |
QUERY_FIELD_DENIED |
Invalid field in where |
QUERY_TIMEOUT |
Query took too long |
SPACE_NOT_READY |
Space is still provisioning |
Error response body: { request_id, code, message }
HTTP status code is carried by the response itself, not duplicated in the JSON body.
Lessons Learned
Routes use slugs, not IDs
All public routes use workspace_slug + space_slug from dots.json, not internal IDs. Call /api/workspaces/{workspace_slug}/spaces/{space_slug}/... for all graph, query, node, edge, and related-node requests.