(HTTP Endpoints for Frontend and External Clients)
This document covers:
- All HTTP endpoints and their parameters
- Request/response formats
- Authentication requirements
- Error handling
- Rate limiting This document does NOT cover:
- MCP tools (see
docs/specs/MCP_SPEC.md) - WebSocket connections
- Internal service-to-service communication
Development:
http://localhost:3080
Production:
https://neotoma.fly.dev
All endpoints (except /health, /openapi.yaml, and MCP OAuth public endpoints) require Bearer token authentication:
Authorization: Bearer <NEOTOMA_BEARER_TOKEN>Token Configuration:
- Set via
NEOTOMA_BEARER_TOKENenvironment variable (preferred) or legacyACTIONS_BEARER_TOKEN - Use strong random token in production
- Never commit tokens to git
- When encryption is enabled (
NEOTOMA_ENCRYPTION_ENABLED=true), use the key-derived MCP token instead:neotoma auth mcp-token
OAuth endpoints for MCP client authentication (no bearer token required for public endpoints).
Start OAuth authorization flow for MCP client.
Endpoint: POST /mcp/oauth/initiate
Request:
{
"connection_id": "cursor-2025-01-21-abc123",
"client_name": "Cursor"
}Parameters:
connection_id: Unique identifier for this connection (required)client_name: Name of MCP client (optional, e.g., "Cursor", "Claude Code")
Response: 200 OK
{
"auth_url": "https://your-server/mcp/oauth/authorize?...",
"connection_id": "cursor-2025-01-21-abc123",
"expires_at": "2025-01-21T10:10:00Z"
}Errors:
400: Missing or invalid connection_id500: Failed to create OAuth state
Handles OAuth authorization callback.
Endpoint: GET /mcp/oauth/callback
Query Parameters:
code: Authorization code from OAuth provider (required)state: OAuth state token (required)
Response: Redirects to frontend with connection status
- Success:
{FRONTEND_URL}/mcp-setup?connection_id={id}&status=success - Error:
{FRONTEND_URL}/mcp-setup?status=error&error={message}
Errors:
400: Missing code or state500: Token exchange or storage failed
Check status of MCP OAuth connection.
Endpoint: GET /mcp/oauth/status
Query Parameters:
connection_id: Connection identifier (required)
Response: 200 OK
{
"status": "active",
"connection_id": "cursor-2025-01-21-abc123"
}Status Values:
pending: OAuth flow initiated but not completedactive: Connection active and ready to useexpired: Connection revoked or OAuth flow timed out
Errors:
400: Missing connection_id500: Failed to query status
List user's active MCP OAuth connections (authenticated).
Endpoint: GET /mcp/oauth/connections
Headers:
Authorization: Bearer <SESSION_TOKEN>Response: 200 OK
{
"connections": [
{
"connectionId": "cursor-2025-01-21-abc123",
"clientName": "Cursor",
"createdAt": "2025-01-21T10:00:00Z",
"lastUsedAt": "2025-01-21T11:30:00Z"
}
]
}Errors:
401: Missing or invalid bearer token500: Failed to fetch connections
Revoke an OAuth connection (authenticated).
Endpoint: DELETE /mcp/oauth/connections/:connection_id
Headers:
Authorization: Bearer <SESSION_TOKEN>Response: 200 OK
{
"success": true
}Errors:
401: Missing or invalid bearer token500: Failed to revoke connection
Use MCP actions instead:
store()- For storing source (unstructured files or structured data)correct()- For correctionsmerge_entities()- For entity mergingretrieve_entities()- For querying entities- See
docs/specs/MCP_SPEC.mdfor complete MCP action reference
Query records with filters and search.
Endpoint: POST /retrieve_records
Request:
{
"type": "note",
"properties": {
"tags": ["important"]
},
"search": ["meeting", "notes"],
"search_mode": "both",
"limit": 20,
"offset": 0,
"include_total_count": true
}Response: 200 OK
{
"records": [
{
"id": "uuid-1",
"type": "note",
"properties": { "text": "Meeting notes", "tags": ["important"] }
}
],
"total_count": 42
}Parameters:
type: Filter by record type (optional)properties: Filter by property values (optional, JSON object)search: Array of search terms (optional)search_mode:"keyword","semantic", or"both"(default:"both")limit: Maximum results (default: 100, max: 1000)offset: Pagination offset (default: 0)include_total_count: Include total count in response (default: false) Errors:400: Invalid request (invalid search_mode)401: Missing or invalid bearer token
Upload a file and optionally create/update a record.
Endpoint: POST /upload_file
Content-Type: multipart/form-data
Form Fields:
file: File to upload (required)record_id: Existing record ID to attach file to (optional)properties: JSON string of properties (optional)bucket: Storage bucket name (optional, default:files) Request Example:
curl -X POST http://localhost:3080/upload_file \
-H "Authorization: Bearer $TOKEN" \
-F "[email protected]" \
-F "properties={\"title\":\"My Document\"}"Response: 201 Created or 200 OK
{
"id": "uuid-here",
"type": "pdf_document",
"properties": {
"title": "My Document",
"file_name": "document.pdf",
"file_size": 102400
},
"file_urls": ["/api/files/uuid/document.pdf"],
"created_at": "2024-01-01T12:00:00Z"
}Behavior:
- If
record_idprovided: Attach file to existing record - If
record_idomitted: Auto-analyze file and create new record - CSV files: Create one record per row Errors:
400: Invalid file (too large, unsupported type)401: Missing or invalid bearer token413: File too large (>50MB)
Generate signed URL for file access.
Endpoint: GET /get_file_url
Query Parameters:
file_path: Path to file in storage (required)expires_in: URL expiration in seconds (optional, default: 3600) Request:
GET /get_file_url?file_path=files/uuid/document.pdf&expires_in=7200
Authorization: Bearer <token>Response: 200 OK
{
"url": "/api/files/uuid/document.pdf?token=...",
"expires_at": "2024-01-01T14:00:00Z"
}Errors:
400: Invalid request (missingfile_path)404: File not found401: Missing or invalid bearer token
Check if server is running.
Endpoint: GET /health
Request:
GET /healthResponse: 200 OK
{
"status": "ok",
"timestamp": "2024-01-01T12:00:00Z"
}No authentication required.
Get OpenAPI spec for API documentation.
Endpoint: GET /openapi.yaml
Request:
GET /openapi.yamlResponse: 200 OK
openapi: 3.0.0
info:
title: Neotoma API
version: 1.0.0
...No authentication required.
All errors follow this format:
{
"error_code": "INGESTION_FILE_TOO_LARGE",
"message": "File exceeds maximum size of 50MB",
"details": {
"file_size": 52428800,
"max_size": 52428800
},
"trace_id": "trace-uuid",
"timestamp": "2024-01-01T12:00:00Z"
}HTTP Status Codes:
200: Success201: Created400: Bad Request (client error)401: Unauthorized (missing/invalid token)403: Forbidden (insufficient permissions)404: Not Found409: Conflict (duplicate record)413: Payload Too Large429: Too Many Requests (rate limited)500: Internal Server Error503: Service Unavailable Seedocs/reference/error_codes.mdfor complete error code reference.
Current Limits (MVP):
- No explicit rate limiting (post-MVP feature)
- Recommended: <100 requests/second per client Future Limits:
- Per-user rate limits
- Per-endpoint rate limits
- Rate limit headers in responses
Use limit and offset for pagination:
{
"limit": 20,
"offset": 0
}Best Practices:
- Use
limit≤ 100 for optimal performance - Use
include_total_count: truefor UI pagination - Implement cursor-based pagination (post-MVP)
Load when:
- Implementing frontend API calls
- Integrating external clients
- Debugging API issues
- Understanding request/response formats
docs/specs/MCP_SPEC.md— MCP tool equivalentsdocs/reference/error_codes.md— Error code referencedocs/subsystems/errors.md— Error handling patterns
- Always include Authorization header — Except
/healthand/openapi.yaml - Validate request format — Check required fields
- Handle errors gracefully — Check error_code, not just HTTP status
- Respect rate limits — Implement exponential backoff
- Use pagination — Don't fetch all records at once
- Calling endpoints without authentication (except health/openapi)
- Ignoring error responses
- Fetching unlimited records (use pagination)
- Hardcoding bearer tokens in frontend code
- Bypassing rate limits