# IVGraph API — Full Documentation > IVGraph visualizes your Notion workspace as an interactive 3D graph. > This document contains complete API reference with request/response examples. --- # Authentication IVGraph uses two authentication methods: JWT tokens (for web users via Notion OAuth) and Service Tokens (for LLM agents and automation). ## Notion OAuth (JWT) IVGraph authenticates users through Notion's OAuth2 flow. There is no username/password registration. ### Flow ``` User clicks "Sign in with Notion" | v GET /api/integrations/auth/notion/ | -> Redirects to Notion consent screen v User authorizes IVGraph in Notion | v Notion redirects to /api/integrations/auth/notion/callback?code=... | -> Backend exchanges code for Notion token | -> Creates/updates Django user | -> Issues JWT tokens v Redirect to dashboard with tokens in URL fragment ``` ### JWT Tokens | Token | Lifetime | Purpose | |-------|----------|---------| | Access token | 4 hours | API authentication | | Refresh token | 7 days | Get new access token | **Using the access token:** ```bash curl -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \ "https://ivgraph.com/api/notion-graph/" ``` **Refreshing an expired token:** ```bash curl -X POST "https://ivgraph.com/api/auth/refresh/" \ -H "Content-Type: application/json" \ -d '{"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."}' ``` Response: ```json {"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."} ``` **Logout (blacklist refresh token):** ```bash curl -X POST "https://ivgraph.com/api/auth/logout/" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"refresh": ""}' ``` ### Auth Endpoints | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/integrations/auth/notion/` | Start Notion OAuth flow | | GET | `/api/integrations/auth/notion/callback` | OAuth callback (internal) | | GET | `/api/integrations/auth/notion/status` | Check OAuth status | | POST | `/api/auth/refresh/` | Refresh access token | | POST | `/api/auth/logout/` | Blacklist refresh token | | GET | `/api/auth/validate/` | Check if token is valid | | GET | `/api/auth/me/` | Get current user info | ## Service Tokens (for LLM agents) Service tokens provide scoped, long-lived access to the V1 read-only API. Designed for LLM agents, automation scripts, and CI/CD pipelines. ### Token Format - Prefix: `ivg_` (e.g., `ivg_a1b2c3d4e5f6...`) - Length: 32 random characters after prefix - Storage: only SHA-256 hash stored in database — raw token shown once at creation ### Creating a Token **Via API (requires JWT auth):** ```bash curl -X POST "https://ivgraph.com/api/tokens/" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "My LLM Agent", "scopes": ["graph:search", "graph:read", "graph:neighbors"], "workspace_ids": ["your-workspace-uuid"], "expires_in_days": 90 }' ``` Response (token shown only once!): ```json { "id": 1, "name": "My LLM Agent", "token": "ivg_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6", "prefix": "ivg_a1b2c3", "scopes": ["graph:search", "graph:read", "graph:neighbors"], "workspace_ids": ["your-workspace-uuid"], "is_active": true, "created_at": "2026-04-10T12:00:00Z", "expires_at": "2026-07-09T12:00:00Z" } ``` ### Scopes | Scope | Permits | |-------|---------| | `graph:search` | Search nodes by title | | `graph:read` | Read node details and page content | | `graph:neighbors` | Get neighbors and traverse paths | ### Limits - Maximum 10 tokens per user - Each token must have at least one scope - Each token must have at least one workspace_id (deny-by-default) - Optional expiration (`expires_in_days`) — omit for no expiry ### Token Management | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/tokens/` | List your tokens (no raw values) | | POST | `/api/tokens/` | Create new token | | DELETE | `/api/tokens//` | Revoke token (permanent) | --- # V1 LLM API Read-only API for LLM agents, automation scripts, and integrations. Provides access to Notion workspace graph data — search, node details, page content, and graph traversal. ## Overview | Property | Value | |----------|-------| | Base URL | `/v1/graph/` | | Authentication | Service Token (`Authorization: Bearer ivg_*`) | | Format | JSON | | Rate Limits | 20 req/10s burst, 600 req/15min window | | Self-describing spec | `GET /v1/` (no auth required) | ## Common Parameters All endpoints require `workspace_id`: | Param | Type | Required | Description | |-------|------|----------|-------------| | workspace_id | string (UUID) | yes | Notion workspace UUID. Must be in token's allowed workspace_ids. | ## Endpoints --- ### GET /v1/graph/search/ Search nodes by title across a workspace. Uses Elasticsearch when available, falls back to in-memory matching. **Scope:** `graph:search` **Parameters:** | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | q | string | yes | — | Search query (max 256 chars) | | workspace_id | string | yes | — | Target workspace UUID | | limit | int | no | 20 | Results per page (max 50) | | type | string | no | all | Filter: `page`, `database`, or `user` | **Example request:** ```bash curl -H "Authorization: Bearer ivg_your_token" \ "https://ivgraph.com/v1/graph/search?q=project&workspace_id=abc-123&limit=5" ``` **Response:** ```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} ] } ``` **Errors:** | Status | Meaning | |--------|---------| | 400 | Missing `q` or `workspace_id`, query too long, or disallowed characters | | 401 | Missing or invalid service token | | 403 | Token lacks `graph:search` scope or workspace not in allowed list | | 404 | No synced data for this workspace | | 429 | Rate limit exceeded | --- ### GET /v1/graph/nodes/{node_id}/ Get a single node with all safe fields: title, type, URL, icon, connection counts, pagerank, and properties. **Scope:** `graph:read` **Parameters:** | Param | Type | Required | Description | |-------|------|----------|-------------| | node_id | UUID | yes | Node ID (in URL path) | | workspace_id | string | yes | Target workspace UUID | **Example request:** ```bash curl -H "Authorization: Bearer ivg_your_token" \ "https://ivgraph.com/v1/graph/nodes/550e8400-e29b-41d4-a716-446655440001/?workspace_id=abc-123" ``` **Response:** ```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": {} } } ``` **Errors:** | Status | Meaning | |--------|---------| | 400 | Invalid node_id format or missing workspace_id | | 401 | Missing or invalid service token | | 403 | Token lacks `graph:read` scope or workspace not allowed | | 404 | Node not found or no synced data for workspace | | 429 | Rate limit exceeded | --- ### GET /v1/graph/content/{page_id}/ Get page content as markdown and plain text. Content is extracted from Notion blocks synced via Airbyte. **Scope:** `graph:read` **Parameters:** | Param | Type | Required | Description | |-------|------|----------|-------------| | page_id | UUID | yes | Page ID (in URL path) | | workspace_id | string | yes | Target workspace UUID | **Example request:** ```bash curl -H "Authorization: Bearer ivg_your_token" \ "https://ivgraph.com/v1/graph/content/550e8400-e29b-41d4-a716-446655440001/?workspace_id=abc-123" ``` **Response:** ```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 } ``` **Errors:** | Status | Meaning | |--------|---------| | 400 | Invalid page_id format or missing workspace_id | | 401 | Missing or invalid service token | | 403 | Token lacks `graph:read` scope or workspace not allowed | | 404 | Page not found or no synced data for workspace | | 429 | Rate limit exceeded | --- ### GET /v1/graph/neighbors/{node_id}/ BFS traversal returning neighbors and edges up to a given depth. Useful for exploring local graph structure around a node. **Scope:** `graph:neighbors` **Parameters:** | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | node_id | UUID | yes | — | Starting node ID (in URL path) | | workspace_id | string | yes | — | Target workspace UUID | | depth | int | no | 1 | Traversal depth (max 3) | | limit | int | no | 20 | Max neighbors to return (max 50) | | direction | string | no | both | Edge direction: `in`, `out`, or `both` | **Example request:** ```bash 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" ``` **Response:** ```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"} ] } ``` **Errors:** | Status | Meaning | |--------|---------| | 400 | Invalid node_id format or missing workspace_id | | 401 | Missing or invalid service token | | 403 | Token lacks `graph:neighbors` scope or workspace not allowed | | 404 | Node not found or no synced data for workspace | | 429 | Rate limit exceeded | --- ### POST /v1/graph/traverse/ Find the shortest path between two nodes using BFS. Returns the path as an ordered list of nodes, or null if no path exists within the max depth. **Scope:** `graph:neighbors` **Request body (JSON):** | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | start_id | UUID | yes | — | Starting node | | target_id | UUID | yes | — | Target node | | workspace_id | string | yes | — | Target workspace UUID | | max_depth | int | no | 3 | Max traversal depth (max 3) | **Example request:** ```bash 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" }' ``` **Response (path found):** ```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"} ] } ``` **Response (no path):** ```json { "start": "550e8400-e29b-41d4-a716-446655440001", "target": "550e8400-e29b-41d4-a716-446655440099", "path_length": null, "path": [] } ``` **Errors:** | Status | Meaning | |--------|---------| | 400 | Missing or invalid start_id/target_id/workspace_id | | 401 | Missing or invalid service token | | 403 | Token lacks `graph:neighbors` scope or workspace not allowed | | 404 | Start or target node not found, or no synced data | | 429 | Rate limit exceeded | --- ## Self-Describing Spec **GET /v1/** returns the full API specification as JSON. No authentication required. ```bash curl "https://ivgraph.com/v1/" ``` This returns machine-readable JSON with all endpoints, parameters, response schemas, rate limits, and scopes. Useful for LLM agents to discover the API programmatically. --- # Graph API The Graph API provides access to the full Notion workspace graph — all nodes (pages, databases, users) and their relationships (edges). This is the primary endpoint used by the IVGraph 3D visualization. ## GET /api/notion-graph/ Returns the complete graph for the authenticated user's workspace. **Authentication:** JWT (or no auth for demo mode) **Parameters:** | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | demo | string | no | — | Set to `true` for demo data (no auth) | | workspace_id | UUID | no | primary | Specific workspace to load | | connection_id | UUID | no | — | Specific Airbyte connection | | admin_user | int | no | — | User ID override (staff only) | **Example request:** ```bash # Authenticated user's graph curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/notion-graph/" # Demo mode (no auth) curl "https://ivgraph.com/api/notion-graph/?demo=true" # Specific workspace curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/notion-graph/?workspace_id=abc-123" ``` **Response:** ```json { "nodes": [ { "id": "550e8400-e29b-41d4-a716-446655440001", "type": "page", "title": "Project Alpha", "url": "https://www.notion.so/Project-Alpha-550e8400...", "icon": {"type": "emoji", "emoji": "\ud83d\ude80"}, "connections_in": 5, "connections_out": 12, "connections_total": 17, "pagerank": 0.42 } ], "links": [ { "id": "edge-uuid", "source": "550e8400-e29b-41d4-a716-446655440001", "target": "550e8400-e29b-41d4-a716-446655440002", "type": "reference" } ], "metadata": { "generated_at": "2026-04-10T12:00:00Z", "user": "username", "connection_id": "conn-uuid", "sync_time": "2026-04-10T10:00:00Z" }, "stats": { "nodes": 342, "links": 1205, "total_blocks": 4500 } } ``` **Response when subscription node limit exceeded (informational, graph still returned):** ```json { "nodes": ["..."], "links": ["..."], "metadata": {}, "stats": {}, "node_limit_exceeded": true, "node_limit": 500, "node_count": 742 } ``` **Response when no connection exists:** ```json { "nodes": [], "links": [], "metadata": {"status": "no_connection"}, "stats": {"nodes": 0, "links": 0, "total_blocks": 0} } ``` ## Data Schemas ### Node | Field | Type | Description | |-------|------|-------------| | id | UUID | Notion page/database/user ID | | type | string | `page`, `database`, or `user` | | title | string | Page title or database name | | url | string | Notion URL | | icon | object\|null | Icon data (see below) | | connections_in | int | Incoming edge count | | connections_out | int | Outgoing edge count | | connections_total | int | Total edge count | | pagerank | float | PageRank score (0.0 — 1.0) | ### Icon | Field | Type | Description | |-------|------|-------------| | type | string | `emoji`, `file`, or `external` | | emoji | string | Emoji character (if type=emoji) | | file.url | string | File URL (if type=file) | | external.url | string | External URL (if type=external) | ### Edge | Field | Type | Description | |-------|------|-------------| | id | UUID | Edge identifier | | source | UUID | Source node ID | | target | UUID | Target node ID | | type | string | `reference`, `parent`, `mention`, or `relation` | ### Edge Types | Type | Description | |------|-------------| | `reference` | Page links to another page | | `parent` | Page is a child of database/page | | `mention` | Page mentions another page or user | | `relation` | Database relation property | --- # Search API IVGraph provides multiple search modes powered by Elasticsearch: full-text (BM25), autocomplete, semantic (vector), hybrid (BM25 + vector), and similarity search. **Authentication:** JWT required for all endpoints. **Prerequisite:** Elasticsearch must be running and the workspace must be indexed (happens automatically after sync). ## Full-Text Search ### GET /api/search/ Search nodes by title and content using BM25 ranking. **Parameters:** | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | q | string | yes | — | Search query | | connection_id | UUID | no | user's first | Specific connection to search | | size | int | no | 50 | Max results | | highlight | bool | no | true | Include highlighted snippets | | type | string | no | all | Filter: `page`, `database`, or `user` | **Example:** ```bash curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/?q=project+planning&size=10" ``` **Response:** ```json { "query": "project planning", "total": 8, "results": [ { "id": "550e8400-e29b-41d4-a716-446655440001", "type": "page", "title": "Project Planning Guide", "score": 12.5, "highlight": "...comprehensive project planning methodology..." } ] } ``` ## Autocomplete ### GET /api/search/suggest/ Prefix-based autocomplete for search-as-you-type. **Parameters:** | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | q | string | yes | — | Prefix (min 2 characters) | | size | int | no | 10 | Max suggestions | **Example:** ```bash curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/suggest/?q=pro" ``` **Response:** ```json { "prefix": "pro", "suggestions": [ {"id": "uuid-1", "title": "Project Alpha", "type": "page"}, {"id": "uuid-2", "title": "Product Roadmap", "type": "page"}, {"id": "uuid-3", "title": "Projects Tracker", "type": "database"} ] } ``` ## Semantic Search ### GET /api/search/semantic/ Vector similarity search using embeddings. Finds conceptually similar content, not just keyword matches. **Parameters:** | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | q | string | yes | — | Natural language query | | connection_id | UUID | no | user's first | Connection to search | | size | int | no | 10 | Max results | **Example:** ```bash curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/semantic/?q=how+do+we+handle+user+onboarding" ``` **Response:** ```json { "query": "how do we handle user onboarding", "total": 5, "search_type": "semantic", "results": [ {"id": "uuid-1", "type": "page", "title": "New User Welcome Flow", "score": 0.89}, {"id": "uuid-2", "type": "page", "title": "Onboarding Checklist", "score": 0.82} ] } ``` ## Hybrid Search ### GET /api/search/hybrid/ Combines BM25 keyword search with semantic vector search for best-of-both results. **Parameters:** Same as semantic search. **Example:** ```bash curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/hybrid/?q=authentication+flow" ``` ## Similar Nodes ### GET /api/search/similar/{node_id}/ Find nodes similar to a given node based on vector embeddings. **Parameters:** | Param | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | node_id | UUID | yes | — | Source node ID (in URL path) | | connection_id | UUID | no | user's first | Connection to search | | size | int | no | 10 | Max results | **Example:** ```bash curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/similar/550e8400-e29b-41d4-a716-446655440001/" ``` ## Page Content ### GET /api/search/content/{page_id}/ Get page content as markdown and plain text. **Parameters:** | Param | Type | Required | Description | |-------|------|----------|-------------| | page_id | UUID | yes | Page ID (in URL path) | **Example:** ```bash curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/content/550e8400-e29b-41d4-a716-446655440001/" ``` **Response:** ```json { "page_id": "550e8400-e29b-41d4-a716-446655440001", "title": "Project Alpha", "markdown": "# Project Alpha\n\nMain project page...", "plain_text": "Project Alpha\nMain project page...", "has_content": true } ``` ## Index Management ### POST /api/search/index/ Trigger reindexing of Elasticsearch index for the user's connection. ```bash curl -X POST -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/index/" ``` **Response:** ```json {"message": "Indexing complete", "index_name": "ivg_conn-uuid", "indexed_count": 342} ``` ### DELETE /api/search/index/ Delete search index for the user's connection. ```bash curl -X DELETE -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/index/" ``` ### GET /api/search/connections/ List all search indices for the user. ```bash curl -H "Authorization: Bearer " \ "https://ivgraph.com/api/search/connections/" ``` **Response:** ```json { "user": "username", "connections": [ { "connection_id": "conn-uuid", "schema_name": "airbyte_conn_uuid", "index_name": "ivg_conn-uuid", "document_count": 342, "last_sync": "2026-04-10T10:00:00Z" } ] } ``` ## Errors | Status | Meaning | |--------|---------| | 400 | Missing or invalid query parameters | | 401 | Not authenticated | | 404 | Connection not found or page not found | | 503 | Elasticsearch unavailable | --- # Errors & Rate Limits ## Error Format All API errors return JSON with this structure: ```json { "error": "Short error description", "detail": "Extended description (optional)" } ``` Some endpoints (Django REST Framework defaults) may also return: ```json { "detail": "Error message" } ``` ## HTTP Status Codes | Status | Meaning | When | |--------|---------|------| | 200 | OK | Request succeeded | | 400 | Bad Request | Missing or invalid parameters | | 401 | Unauthorized | Missing, expired, or invalid auth token | | 403 | Forbidden | Token lacks required scope or workspace access | | 404 | Not Found | Resource doesn't exist or workspace has no synced data | | 405 | Method Not Allowed | Wrong HTTP method for endpoint | | 422 | Unprocessable Entity | Request body validation failed | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Internal Server Error | Unexpected server error | | 503 | Service Unavailable | Elasticsearch or other dependency down | ## Rate Limits ### V1 API (Service Tokens) | Tier | Limit | Scope | |------|-------|-------| | Burst | 20 requests / 10 seconds | Per token | | Window | 600 requests / 15 minutes | Per token | ### User API (JWT) | Tier | Limit | Scope | |------|-------|-------| | Authenticated | 100 requests / minute | Per user | | Anonymous | 20 requests / minute | Per IP | ### Rate Limit Headers When rate limited, the response includes: ```http HTTP/1.1 429 Too Many Requests Retry-After: 30 ``` **Handling rate limits:** Wait for the `Retry-After` duration (in seconds) before retrying. Implement exponential backoff for repeated 429 responses. ## Common Error Scenarios ### Authentication Errors ```json // Missing Authorization header {"detail": "Authentication credentials were not provided."} // Invalid or expired JWT {"detail": "Given token not valid for any token type", "code": "token_not_valid"} // Invalid service token {"error": "Invalid or revoked service token."} // Expired service token {"error": "Token has expired."} ``` ### Permission Errors ```json // Token missing required scope {"error": "Token does not have the required scope: graph:search"} // Workspace not in token's allowed list {"error": "Workspace not allowed for this token."} ``` ### Validation Errors ```json // Missing required parameter {"error": "workspace_id is required."} // Invalid UUID format {"error": "Invalid node_id format."} // Query too long (V1 API) {"error": "Query too long (max 256 characters)."} // Disallowed characters in query {"error": "Query contains disallowed characters."} ``` ### Resource Errors ```json // Workspace not synced {"error": "No synced data found for this workspace."} // Node not found {"error": "Node not found."} // Elasticsearch unavailable {"error": "Search service unavailable."} ```