V1 LLM API
LLM 에이전트, 자동화 스크립트, 통합을 위한 읽기 전용 API예요. 노션 워크스페이스의 그래프 데이터에 접근해 검색, 노드 상세 정보 조회, 페이지 콘텐츠 조회, 그래프 탐색을 할 수 있어요.
개요
| 속성 | 값 |
|---|---|
| 기본 URL | /v1/graph/ |
| 인증 | 서비스 토큰 (Authorization: Bearer ivg_*) |
| 형식 | JSON |
| 요청 제한 | 버스트 제한 10초당 20회, 15분 구간당 600회 |
| 자체 설명형 명세 | GET /v1/ (인증 불필요) |
공통 매개변수
모든 엔드포인트에 workspace_id가 필요해요.
| 매개변수 | 유형 | 필수 여부 | 설명 |
|---|---|---|---|
| workspace_id | string (UUID) | 필수 | 노션 워크스페이스 UUID. 토큰의 허용된 workspace_ids 목록에 포함되어 있어야 해요. |
엔드포인트
GET /v1/graph/search/
워크스페이스 전체에서 제목으로 노드를 검색해요. Elasticsearch를 사용할 수 있으면 사용하고, 사용할 수 없으면 메모리 내 데이터 매칭으로 대체해요.
권한 범위: graph:search
매개변수:
| 매개변수 | 유형 | 필수 여부 | 기본값 | 설명 |
|---|---|---|---|---|
| q | string | 필수 | — | 검색어 (최대 256자) |
| workspace_id | string | 필수 | — | 대상 워크스페이스 UUID |
| limit | int | 선택 | 20 | 페이지당 결과 수 (최대 50개) |
| type | string | 선택 | all | 필터: page, database 또는 user |
요청 예시:
curl -H "Authorization: Bearer ivg_your_token" \
"https://ivgraph.com/v1/graph/search?q=project&workspace_id=abc-123&limit=5"
import requests
resp = requests.get(
"https://ivgraph.com/v1/graph/search",
headers={"Authorization": "Bearer ivg_your_token"},
params={"q": "project", "workspace_id": "abc-123", "limit": 5}
)
data = resp.json()
const resp = await fetch(
"https://ivgraph.com/v1/graph/search?q=project&workspace_id=abc-123&limit=5",
{ headers: { Authorization: "Bearer ivg_your_token" } }
);
const data = await resp.json();
응답:
{
"query": "project",
"total": 3,
"results": [
{"id": "550e8400-e29b-41d4-a716-446655440001", "type": "page", "title": "Project Alpha", "score": 0.95},
{"id": "550e8400-e29b-41d4-a716-446655440002", "type": "database", "title": "Projects Tracker", "score": 0.72},
{"id": "550e8400-e29b-41d4-a716-446655440003", "type": "page", "title": "Project Notes", "score": 0.61}
]
}
오류:
| 상태 | 의미 |
|---|---|
| 400 | q 또는 workspace_id 누락, 검색어 길이 초과 또는 허용되지 않는 문자 포함 |
| 401 | 서비스 토큰이 없거나 유효하지 않음 |
| 403 | 토큰에 graph:search 권한 범위가 없거나 워크스페이스가 허용 목록에 없음 |
| 404 | 이 워크스페이스에 동기화된 데이터가 없음 |
| 429 | 요청 제한 초과 |
GET /v1/graph/nodes/{node_id}/
단일 노드를 안전하게 제공할 수 있는 모든 필드와 함께 조회해요. 제목, 유형, URL, 아이콘, 연결 수, pagerank, 속성이 포함돼요.
권한 범위: graph:read
매개변수:
| 매개변수 | 유형 | 필수 여부 | 설명 |
|---|---|---|---|
| node_id | UUID | 필수 | 노드 ID (URL 경로에 포함) |
| workspace_id | string | 필수 | 대상 워크스페이스 UUID |
요청 예시:
curl -H "Authorization: Bearer ivg_your_token" \
"https://ivgraph.com/v1/graph/nodes/550e8400-e29b-41d4-a716-446655440001/?workspace_id=abc-123"
import requests
node_id = "550e8400-e29b-41d4-a716-446655440001"
resp = requests.get(
f"https://ivgraph.com/v1/graph/nodes/{node_id}/",
headers={"Authorization": "Bearer ivg_your_token"},
params={"workspace_id": "abc-123"}
)
node = resp.json()["node"]
const nodeId = "550e8400-e29b-41d4-a716-446655440001";
const resp = await fetch(
`https://ivgraph.com/v1/graph/nodes/${nodeId}/?workspace_id=abc-123`,
{ headers: { Authorization: "Bearer ivg_your_token" } }
);
const { node } = await resp.json();
응답:
{
"node": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"type": "page",
"title": "Project Alpha",
"url": "https://www.notion.so/Project-Alpha-550e8400e29b41d4a716446655440001",
"icon": {"type": "emoji", "emoji": "\ud83d\ude80"},
"connections_in": 5,
"connections_out": 12,
"connections_total": 17,
"pagerank": 0.42,
"properties": {}
}
}
오류:
| 상태 | 의미 |
|---|---|
| 400 | node_id 형식이 유효하지 않거나 workspace_id가 누락됨 |
| 401 | 서비스 토큰이 없거나 유효하지 않음 |
| 403 | 토큰에 graph:read 권한 범위가 없거나 허용되지 않은 워크스페이스임 |
| 404 | 노드를 찾을 수 없거나 워크스페이스에 동기화된 데이터가 없음 |
| 429 | 요청 제한 초과 |
GET /v1/graph/content/{page_id}/
페이지 콘텐츠를 마크다운과 일반 텍스트로 조회해요. 콘텐츠는 Airbyte를 통해 동기화된 노션 블록에서 추출해요.
권한 범위: graph:read
매개변수:
| 매개변수 | 유형 | 필수 여부 | 설명 |
|---|---|---|---|
| page_id | UUID | 필수 | 페이지 ID (URL 경로에 포함) |
| workspace_id | string | 필수 | 대상 워크스페이스 UUID |
요청 예시:
curl -H "Authorization: Bearer ivg_your_token" \
"https://ivgraph.com/v1/graph/content/550e8400-e29b-41d4-a716-446655440001/?workspace_id=abc-123"
import requests
page_id = "550e8400-e29b-41d4-a716-446655440001"
resp = requests.get(
f"https://ivgraph.com/v1/graph/content/{page_id}/",
headers={"Authorization": "Bearer ivg_your_token"},
params={"workspace_id": "abc-123"}
)
content = resp.json()
print(content["markdown"])
const pageId = "550e8400-e29b-41d4-a716-446655440001";
const resp = await fetch(
`https://ivgraph.com/v1/graph/content/${pageId}/?workspace_id=abc-123`,
{ headers: { Authorization: "Bearer ivg_your_token" } }
);
const { markdown, plain_text } = await resp.json();
응답:
{
"page_id": "550e8400-e29b-41d4-a716-446655440001",
"title": "Project Alpha",
"markdown": "# Project Alpha\n\nThis is the main project page.\n\n## Goals\n\n- Launch beta by Q2\n- Reach 100 users",
"plain_text": "Project Alpha\nThis is the main project page.\nGoals\nLaunch beta by Q2\nReach 100 users",
"has_content": true
}
오류:
| 상태 | 의미 |
|---|---|
| 400 | page_id 형식이 유효하지 않거나 workspace_id가 누락됨 |
| 401 | 서비스 토큰이 없거나 유효하지 않음 |
| 403 | 토큰에 graph:read 권한 범위가 없거나 허용되지 않은 워크스페이스임 |
| 404 | 페이지를 찾을 수 없거나 워크스페이스에 동기화된 데이터가 없음 |
| 429 | 요청 제한 초과 |
GET /v1/graph/neighbors/{node_id}/
BFS 탐색으로 지정된 깊이까지 이웃 노드와 간선을 반환해요. 특정 노드 주변의 그래프 구조를 살펴볼 때 유용해요.
권한 범위: graph:neighbors
매개변수:
| 매개변수 | 유형 | 필수 여부 | 기본값 | 설명 |
|---|---|---|---|---|
| node_id | UUID | 필수 | — | 시작 노드 ID (URL 경로에 포함) |
| workspace_id | string | 필수 | — | 대상 워크스페이스 UUID |
| depth | int | 선택 | 1 | 탐색 깊이 (최대 3) |
| limit | int | 선택 | 20 | 반환할 이웃 노드의 최대 수 (최대 50개) |
| direction | string | 선택 | both | 간선 방향: in, out 또는 both |
요청 예시:
curl -H "Authorization: Bearer ivg_your_token" \
"https://ivgraph.com/v1/graph/neighbors/550e8400-e29b-41d4-a716-446655440001/?workspace_id=abc-123&depth=2&limit=10"
import requests
node_id = "550e8400-e29b-41d4-a716-446655440001"
resp = requests.get(
f"https://ivgraph.com/v1/graph/neighbors/{node_id}/",
headers={"Authorization": "Bearer ivg_your_token"},
params={"workspace_id": "abc-123", "depth": 2, "limit": 10}
)
data = resp.json()
print(f"Found {data['total']} neighbors")
const nodeId = "550e8400-e29b-41d4-a716-446655440001";
const resp = await fetch(
`https://ivgraph.com/v1/graph/neighbors/${nodeId}/?workspace_id=abc-123&depth=2&limit=10`,
{ headers: { Authorization: "Bearer ivg_your_token" } }
);
const { neighbors, edges, total } = await resp.json();
응답:
{
"node_id": "550e8400-e29b-41d4-a716-446655440001",
"depth": 2,
"total": 4,
"neighbors": [
{"id": "550e8400-e29b-41d4-a716-446655440010", "type": "page", "title": "Sprint Planning"},
{"id": "550e8400-e29b-41d4-a716-446655440011", "type": "database", "title": "Tasks"},
{"id": "550e8400-e29b-41d4-a716-446655440012", "type": "page", "title": "Design Doc"},
{"id": "550e8400-e29b-41d4-a716-446655440013", "type": "user", "title": "Alice"}
],
"edges": [
{"source": "550e8400-e29b-41d4-a716-446655440001", "target": "550e8400-e29b-41d4-a716-446655440010", "type": "reference"},
{"source": "550e8400-e29b-41d4-a716-446655440001", "target": "550e8400-e29b-41d4-a716-446655440011", "type": "parent"},
{"source": "550e8400-e29b-41d4-a716-446655440010", "target": "550e8400-e29b-41d4-a716-446655440012", "type": "mention"},
{"source": "550e8400-e29b-41d4-a716-446655440012", "target": "550e8400-e29b-41d4-a716-446655440013", "type": "mention"}
]
}
오류:
| 상태 | 의미 |
|---|---|
| 400 | node_id 형식이 유효하지 않거나 workspace_id가 누락됨 |
| 401 | 서비스 토큰이 없거나 유효하지 않음 |
| 403 | 토큰에 graph:neighbors 권한 범위가 없거나 허용되지 않은 워크스페이스임 |
| 404 | 노드를 찾을 수 없거나 워크스페이스에 동기화된 데이터가 없음 |
| 429 | 요청 제한 초과 |
POST /v1/graph/traverse/
BFS로 두 노드 사이의 최단 경로를 찾아요. 경로를 노드가 경로 순서대로 나열된 목록으로 반환하며, 최대 깊이 내에 경로가 없으면 null을 반환해요.
권한 범위: graph:neighbors
요청 본문 (JSON):
| 매개변수 | 유형 | 필수 여부 | 기본값 | 설명 |
|---|---|---|---|---|
| start_id | UUID | 필수 | — | 시작 노드 |
| target_id | UUID | 필수 | — | 대상 노드 |
| workspace_id | string | 필수 | — | 대상 워크스페이스 UUID |
| max_depth | int | 선택 | 3 | 최대 탐색 깊이 (최대 3) |
요청 예시:
curl -X POST "https://ivgraph.com/v1/graph/traverse/" \
-H "Authorization: Bearer ivg_your_token" \
-H "Content-Type: application/json" \
-d '{
"start_id": "550e8400-e29b-41d4-a716-446655440001",
"target_id": "550e8400-e29b-41d4-a716-446655440012",
"workspace_id": "abc-123"
}'
import requests
resp = requests.post(
"https://ivgraph.com/v1/graph/traverse/",
headers={
"Authorization": "Bearer ivg_your_token",
"Content-Type": "application/json"
},
json={
"start_id": "550e8400-e29b-41d4-a716-446655440001",
"target_id": "550e8400-e29b-41d4-a716-446655440012",
"workspace_id": "abc-123"
}
)
path = resp.json()["path"]
const resp = await fetch("https://ivgraph.com/v1/graph/traverse/", {
method: "POST",
headers: {
Authorization: "Bearer ivg_your_token",
"Content-Type": "application/json"
},
body: JSON.stringify({
start_id: "550e8400-e29b-41d4-a716-446655440001",
target_id: "550e8400-e29b-41d4-a716-446655440012",
workspace_id: "abc-123"
})
});
const { path, path_length } = await resp.json();
응답 (경로를 찾은 경우):
{
"start": "550e8400-e29b-41d4-a716-446655440001",
"target": "550e8400-e29b-41d4-a716-446655440012",
"path_length": 2,
"path": [
{"id": "550e8400-e29b-41d4-a716-446655440001", "type": "page", "title": "Project Alpha"},
{"id": "550e8400-e29b-41d4-a716-446655440010", "type": "page", "title": "Sprint Planning"},
{"id": "550e8400-e29b-41d4-a716-446655440012", "type": "page", "title": "Design Doc"}
]
}
응답 (경로가 없는 경우):
{
"start": "550e8400-e29b-41d4-a716-446655440001",
"target": "550e8400-e29b-41d4-a716-446655440099",
"path_length": null,
"path": []
}
오류:
| 상태 | 의미 |
|---|---|
| 400 | start_id/target_id/workspace_id가 누락되었거나 유효하지 않음 |
| 401 | 서비스 토큰이 없거나 유효하지 않음 |
| 403 | 토큰에 graph:neighbors 권한 범위가 없거나 허용되지 않은 워크스페이스임 |
| 404 | 시작 노드 또는 대상 노드를 찾을 수 없거나 동기화된 데이터가 없음 |
| 429 | 요청 제한 초과 |
자체 설명형 명세
GET /v1/은 전체 API 명세를 JSON으로 반환해요. 인증은 필요하지 않아요.
curl "https://ivgraph.com/v1/"
모든 엔드포인트, 매개변수, 응답 스키마, 요청 제한, 권한 범위가 담긴 기계 판독 가능한 JSON을 반환해요. LLM 에이전트가 프로그래밍 방식으로 API를 파악할 때 유용해요.