# Create Document Source: https://surfacedocs.mintlify.app/api-reference/documents/create POST /v1/documents # Create Document ``` POST /v1/documents ``` Create a new document with blocks. Requires the `documents:write` role. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------ | | `X-API-Key` | Yes | Your API key | | `Content-Type` | Yes | `application/json` | ### Body | Field | Type | Required | Description | | ------------------- | ------ | -------- | ---------------------------------------------------------------------------------------- | | `title` | string | Yes | Document title (max 500 characters) | | `blocks` | array | Yes | Array of content blocks | | `blocks[].type` | string | Yes | Block type: `heading`, `paragraph`, `code`, `list`, `quote`, `table`, `image`, `divider` | | `blocks[].content` | string | Yes | Block content | | `blocks[].metadata` | object | No | Block metadata (e.g., `level` for headings) | | `folder_id` | string | No | Target folder ID. Uses root folder if omitted. | | `content_type` | string | No | Default: `"markdown"` | | `visibility` | string | No | `"private"` (default) or `"public"` | | `metadata` | object | No | Document-level metadata | ### Example ```bash theme={null} curl -X POST https://api.surfacedocs.dev/v1/documents \ -H "X-API-Key: sd_live_..." \ -H "Content-Type: application/json" \ -d '{ "title": "API Guide", "blocks": [ {"type": "heading", "content": "Overview", "metadata": {"level": 1}}, {"type": "paragraph", "content": "Welcome to the API."} ] }' ``` ## Response **Status: 201 Created** ```json theme={null} { "id": "doc_abc123", "url": "https://app.surfacedocs.dev/d/doc_abc123", "folder_id": "fld_xyz", "title": "API Guide", "content_type": "markdown", "block_count": 2, "metadata": null, "visibility": "private", "created_at": "2025-01-15T10:30:00Z" } ``` ## Quota Headers Responses include document quota information: | Header | Description | | --------------- | ----------------------------------- | | `X-Quota-Used` | Documents created this month | | `X-Quota-Limit` | Monthly document limit | | `X-Quota-Reset` | Date when quota resets (YYYY-MM-DD) | # Delete Document Source: https://surfacedocs.mintlify.app/api-reference/documents/delete DELETE /v1/documents/{id} # Delete Document ``` DELETE /v1/documents/{document_id} ``` Delete a document and all its blocks. Requires the `documents:write` role. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `X-API-Key` | Yes | Your API key | ### Path Parameters | Parameter | Type | Description | | ------------- | ------ | --------------- | | `document_id` | string | The document ID | ### Example ```bash theme={null} curl -X DELETE https://api.surfacedocs.dev/v1/documents/doc_abc123 \ -H "X-API-Key: sd_live_..." ``` ## Response **Status: 204 No Content** No response body. ## Errors | Code | Description | | ---- | -------------------------------------- | | 404 | Document not found | | 403 | Access denied (not the document owner) | # Get Document Source: https://surfacedocs.mintlify.app/api-reference/documents/get GET /v1/documents/{id} # Get Document ``` GET /v1/documents/{document_id} ``` Retrieve a document and all its blocks. Requires the `documents:read` role. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `X-API-Key` | Yes | Your API key | ### Path Parameters | Parameter | Type | Description | | ------------- | ------ | --------------- | | `document_id` | string | The document ID | ### Example ```bash theme={null} curl https://api.surfacedocs.dev/v1/documents/doc_abc123 \ -H "X-API-Key: sd_live_..." ``` ## Response **Status: 200 OK** ```json theme={null} { "id": "doc_abc123", "url": "https://app.surfacedocs.dev/d/doc_abc123", "folder_id": "fld_xyz", "title": "API Guide", "content_type": "markdown", "metadata": null, "visibility": "private", "blocks": [ { "id": "blk_001", "order": 0, "type": "heading", "content": "Overview", "metadata": {"level": 1} }, { "id": "blk_002", "order": 1, "type": "paragraph", "content": "Welcome to the API.", "metadata": null } ], "created_at": "2025-01-15T10:30:00Z", "updated_at": "2025-01-15T10:30:00Z" } ``` ## Errors | Code | Description | | ---- | -------------------------------------- | | 404 | Document not found | | 403 | Access denied (not the document owner) | # Search Documents Source: https://surfacedocs.mintlify.app/api-reference/documents/search Search documents by title prefix and/or tag # Search Documents ``` GET /v1/documents/search ``` Search documents by title prefix and/or tag. At least one of `q` or `tag` is required. Results are scoped to the authenticated user. ## Query Parameters | Parameter | Type | Required | Description | | ----------- | --------- | -------- | --------------------------------------------- | | `q` | `string` | No\* | Title prefix to search for (case-insensitive) | | `tag` | `string` | No\* | Exact tag to match from `metadata.tags` | | `folder_id` | `string` | No | Scope results to a specific folder | | `limit` | `integer` | No | Max results (default: 20, max: 50) | | `offset` | `integer` | No | Number of results to skip | At least one of `q` or `tag` is required. **Required role:** `documents:read` ## Example Request ```bash theme={null} curl "https://api.surfacedocs.dev/v1/documents/search?q=API&tag=guide&limit=10" \ -H "X-API-Key: sd_live_..." ``` ## Response ```json theme={null} { "results": [ { "id": "doc_abc123", "url": "https://app.surfacedocs.dev/d/doc_abc123", "folder_id": "fld_xyz", "title": "API Documentation", "content_type": "markdown", "block_count": 15, "metadata": { "tags": ["api", "guide"], "source": "claude-3" }, "visibility": "private", "current_version": 3, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-02T00:00:00Z" } ], "count": 1, "query": "API", "tag": "guide", "folder_id": null } ``` ## Response Fields | Field | Type | Description | | ----------- | ---------------- | -------------------------- | | `results` | `array` | List of matching documents | | `count` | `integer` | Number of results returned | | `query` | `string \| null` | The title query used | | `tag` | `string \| null` | The tag filter used | | `folder_id` | `string \| null` | The folder scope used | Each result in `results` contains: | Field | Type | Description | | ----------------- | ----------------- | ----------------------------------------- | | `id` | `string` | Document ID | | `url` | `string` | URL to view the document | | `folder_id` | `string` | Parent folder ID | | `title` | `string` | Document title | | `content_type` | `string` | Content type (`markdown`, `html`, `text`) | | `block_count` | `integer` | Number of content blocks | | `metadata` | `object \| null` | Document metadata including tags | | `visibility` | `string` | Document visibility | | `current_version` | `integer \| null` | Current version number | | `created_at` | `string` | ISO 8601 creation timestamp | | `updated_at` | `string` | ISO 8601 last update timestamp | ## Search Behavior * **Title search (`q`):** Case-insensitive prefix match. Searching `q=api` matches "API Documentation", "api-guide", etc. * **Tag search (`tag`):** Exact match against the `metadata.tags` array. * **Combined:** When both `q` and `tag` are provided, results must match both (AND logic). * **Folder scope:** When `folder_id` is provided, only documents in that folder are searched. ## Errors | Code | Description | | ---- | ----------------------------------- | | 422 | Neither `q` nor `tag` provided | | 401 | Invalid or missing API key | | 403 | API key lacks `documents:read` role | | 404 | Specified `folder_id` not found | # Create Folder Source: https://surfacedocs.mintlify.app/api-reference/folders/create POST /v1/folders # Create Folder ``` POST /v1/folders ``` Create a new folder. Requires the `folders:write` role. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------ | | `X-API-Key` | Yes | Your API key | | `Content-Type` | Yes | `application/json` | ### Body | Field | Type | Required | Description | | ----------- | ------ | -------- | ---------------------------- | | `name` | string | Yes | Folder name | | `parent_id` | string | No | Parent folder ID for nesting | ### Example ```bash theme={null} curl -X POST https://api.surfacedocs.dev/v1/folders \ -H "X-API-Key: sd_live_..." \ -H "Content-Type: application/json" \ -d '{"name": "Engineering"}' ``` ## Response **Status: 201 Created** ```json theme={null} { "id": "fld_abc123", "name": "Engineering", "parent_id": null, "path": "/Engineering", "depth": 0, "created_at": "2025-01-15T10:30:00Z" } ``` ## Errors | Code | Description | | ---- | ----------------------- | | 404 | Parent folder not found | # List Folders Source: https://surfacedocs.mintlify.app/api-reference/folders/list GET /v1/folders # List Folders ``` GET /v1/folders ``` List folders for the authenticated user. Requires the `folders:read` role. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `X-API-Key` | Yes | Your API key | ### Query Parameters | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ----------------------------------------------- | | `parent_id` | string | No | Filter by parent folder. Omit for root folders. | ### Example ```bash theme={null} # List root folders curl https://api.surfacedocs.dev/v1/folders \ -H "X-API-Key: sd_live_..." # List subfolders curl "https://api.surfacedocs.dev/v1/folders?parent_id=fld_abc123" \ -H "X-API-Key: sd_live_..." ``` ## Response **Status: 200 OK** ```json theme={null} { "folders": [ { "id": "fld_abc123", "name": "Engineering", "parent_id": null, "path": "/Engineering", "depth": 0, "created_at": "2025-01-15T10:30:00Z" }, { "id": "fld_def456", "name": "Reports", "parent_id": null, "path": "/Reports", "depth": 0, "created_at": "2025-01-16T08:00:00Z" } ] } ``` # API Reference Source: https://surfacedocs.mintlify.app/api-reference/introduction REST API for SurfaceDocs # API Reference The SurfaceDocs REST API lets you create, read, and delete documents and manage folders. ## Base URL ``` https://api.surfacedocs.dev ``` ## Authentication All requests require an API key passed in the `X-API-Key` header: ```bash theme={null} curl https://api.surfacedocs.dev/v1/documents/doc_abc123 \ -H "X-API-Key: sd_live_..." ``` Get your API key from [app.surfacedocs.dev](https://app.surfacedocs.dev). ## Rate Limits Rate limits depend on your plan: | Plan | Per Minute | Per Day | | ---- | ---------- | ------- | | Free | 20 | 1,000 | | Pro | 300 | 50,000 | Every response includes rate limit headers: | Header | Description | | ----------------------- | ------------------------------------ | | `X-RateLimit-Limit` | Requests allowed per minute | | `X-RateLimit-Remaining` | Requests remaining this minute | | `X-RateLimit-Reset` | Unix timestamp when the limit resets | When rate limited, you'll receive a `429` response with a `Retry-After` header. ## Errors All errors return a consistent JSON format: ```json theme={null} { "error": { "code": "VALIDATION_ERROR", "message": "Invalid document format", "details": {} } } ``` | HTTP Code | Description | | --------- | -------------------------- | | 400 | Validation error | | 401 | Invalid or missing API key | | 403 | Insufficient permissions | | 404 | Resource not found | | 429 | Rate limit exceeded | # Get Version Source: https://surfacedocs.mintlify.app/api-reference/versions/get GET /v1/documents/{document_id}/versions/{version_number} # Get Version ``` GET /v1/documents/{document_id}/versions/{version_number} ``` Get a specific version of a document with its full content and blocks. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `X-API-Key` | Yes | Your API key | ### Path Parameters | Parameter | Type | Required | Description | | ---------------- | ------- | -------- | ------------------ | | `document_id` | string | Yes | The document ID | | `version_number` | integer | Yes | The version number | ### Example ```bash theme={null} curl https://api.surfacedocs.dev/v1/documents/doc_abc123/versions/1 \ -H "X-API-Key: sd_live_..." ``` ## Response **Status: 200 OK** ```json theme={null} { "version": 1, "title": "Daily Report — Feb 13", "content_type": "markdown", "metadata": {"generated_at": "2026-02-13"}, "blocks": [ { "id": "blk_001", "order": 0, "type": "heading", "content": "Summary", "metadata": {"level": 1} }, { "id": "blk_002", "order": 1, "type": "paragraph", "content": "Revenue: $38,000", "metadata": null } ], "created_at": "2026-02-13T10:00:00Z" } ``` # List Versions Source: https://surfacedocs.mintlify.app/api-reference/versions/list GET /v1/documents/{document_id}/versions # List Versions ``` GET /v1/documents/{document_id}/versions ``` List all versions of a document. Returns version summaries in chronological order. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `X-API-Key` | Yes | Your API key | ### Path Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------- | | `document_id` | string | Yes | The document ID | ### Example ```bash theme={null} curl https://api.surfacedocs.dev/v1/documents/doc_abc123/versions \ -H "X-API-Key: sd_live_..." ``` ## Response **Status: 200 OK** ```json theme={null} { "versions": [ { "version": 1, "title": "Daily Report — Feb 13", "block_count": 5, "created_at": "2026-02-13T10:00:00Z" }, { "version": 2, "title": "Daily Report — Feb 14", "block_count": 6, "created_at": "2026-02-14T10:30:00Z" } ], "current_version": 2 } ``` # Push Version Source: https://surfacedocs.mintlify.app/api-reference/versions/push POST /v1/documents/{document_id}/versions # Push Version ``` POST /v1/documents/{document_id}/versions ``` Push a new version of an existing document. The document URL stays the same — viewers always see the latest version. ## Request ### Headers | Header | Required | Description | | -------------- | -------- | ------------------ | | `X-API-Key` | Yes | Your API key | | `Content-Type` | Yes | `application/json` | ### Path Parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------- | | `document_id` | string | Yes | The document ID | ### Body | Field | Type | Required | Description | | ------------------- | ------ | -------- | ----------------------- | | `title` | string | Yes | Document title | | `blocks` | array | Yes | Array of content blocks | | `blocks[].type` | string | Yes | Block type | | `blocks[].content` | string | Yes | Block content | | `blocks[].metadata` | object | No | Block metadata | | `content_type` | string | No | Default: `"markdown"` | | `metadata` | object | No | Document-level metadata | ### Example ```bash theme={null} curl -X POST https://api.surfacedocs.dev/v1/documents/doc_abc123/versions \ -H "X-API-Key: sd_live_..." \ -H "Content-Type: application/json" \ -d '{ "title": "Daily Report — Feb 14", "blocks": [ {"type": "heading", "content": "Summary", "metadata": {"level": 1}}, {"type": "paragraph", "content": "Revenue: $42,000"} ], "metadata": {"generated_at": "2026-02-14"} }' ``` ## Response **Status: 201 Created** ```json theme={null} { "id": "doc_abc123", "url": "https://app.surfacedocs.dev/d/doc_abc123", "version": 2, "version_count": 2, "title": "Daily Report — Feb 14", "block_count": 2, "created_at": "2026-02-14T10:30:00Z" } ``` # Restore Version Source: https://surfacedocs.mintlify.app/api-reference/versions/restore POST /v1/documents/{document_id}/versions/{version_number}/restore # Restore Version ``` POST /v1/documents/{document_id}/versions/{version_number}/restore ``` Restore a previous version as the new latest. This creates a new version with the content from the specified version. ## Request ### Headers | Header | Required | Description | | ----------- | -------- | ------------ | | `X-API-Key` | Yes | Your API key | ### Path Parameters | Parameter | Type | Required | Description | | ---------------- | ------- | -------- | ----------------------------- | | `document_id` | string | Yes | The document ID | | `version_number` | integer | Yes | The version number to restore | ### Example ```bash theme={null} curl -X POST https://api.surfacedocs.dev/v1/documents/doc_abc123/versions/1/restore \ -H "X-API-Key: sd_live_..." ``` ## Response **Status: 201 Created** ```json theme={null} { "id": "doc_abc123", "url": "https://app.surfacedocs.dev/d/doc_abc123", "version": 4, "version_count": 4, "title": "Daily Report — Feb 13", "block_count": 2, "created_at": "2026-02-14T11:00:00Z" } ``` # SurfaceDocs Source: https://surfacedocs.mintlify.app/index The output layer for AI pipelines # The output layer for AI pipelines SurfaceDocs turns AI-generated content into shareable, hosted documents. One API call, instant URL, zero infrastructure. ```bash theme={null} pip install surfacedocs ``` ```python theme={null} from surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT docs = SurfaceDocs(api_key="sd_live_...") result = docs.save(llm_output) print(result.url) # https://app.surfacedocs.dev/d/abc123 ``` Install the SDK and save your first document in under 5 minutes. Full reference for the SurfaceDocs Python client. REST API endpoints for documents and folders. ## Why SurfaceDocs? Every AI pipeline ends the same way: the model generates something valuable, and then you need to figure out where to put it. SurfaceDocs gives you: * **Instant shareable URLs** — `docs.save(output)` returns a link anyone can view * **No infrastructure** — No viewers to build, no hosting to manage * **Built for AI output** — Block-based schema that matches how LLMs think * **Document versioning** — Push new versions to the same URL, view history, restore old versions * **Works with any LLM** — OpenAI, Gemini, Anthropic, or any model Sign up at app.surfacedocs.dev to get started. # Pricing Source: https://surfacedocs.mintlify.app/pricing Free and Pro plan comparison # Pricing **\$0/month** Get started with SurfaceDocs at no cost. * 10 documents per month * 20 requests/minute rate limit * 1,000 requests/day rate limit * Unlimited folders * API key access **Pro plan** For teams and production pipelines. * 1,000 documents per month * 300 requests/minute rate limit * 50,000 requests/day rate limit * Folder sharing * Priority support ## Plan comparison | Feature | Free | Pro | | ----------------------- | ----- | ------ | | Documents per month | 10 | 1,000 | | Rate limit (per minute) | 20 | 300 | | Rate limit (per day) | 1,000 | 50,000 | | Folders | ✅ | ✅ | | Folder sharing | ❌ | ✅ | | API access | ✅ | ✅ | ## Quotas Document quotas reset on the 1st of each month. Usage is tracked per account, not per API key. Rate limit headers are included in every API response: ``` X-RateLimit-Limit: 20 X-RateLimit-Remaining: 19 X-RateLimit-Reset: 1706745600 X-Quota-Used: 3 X-Quota-Limit: 10 X-Quota-Reset: 2025-03-01 ``` Manage your plan in the SurfaceDocs dashboard. # Anthropic Source: https://surfacedocs.mintlify.app/providers/anthropic Generate documents with Claude using tool use # Anthropic Claude supports structured output via tool use. Pass `DOCUMENT_SCHEMA` as a tool's `input_schema` and force the model to use it with `tool_choice`. ```python theme={null} from surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT import anthropic client = anthropic.Anthropic() docs = SurfaceDocs() response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, system=SYSTEM_PROMPT, messages=[ {"role": "user", "content": "Write documentation for user authentication"}, ], tools=[{ "name": "create_document", "description": "Create a structured document", "input_schema": DOCUMENT_SCHEMA, }], tool_choice={"type": "tool", "name": "create_document"}, ) # Extract the tool use block tool_use = next(b for b in response.content if b.type == "tool_use") result = docs.save(tool_use.input) print(f"Saved: {result.url}") ``` Claude doesn't have a native `response_format` like OpenAI. Instead, we use tool use to enforce the schema. Setting `tool_choice` to the specific tool name guarantees Claude will output in the correct format. ## How it works 1. `SYSTEM_PROMPT` tells Claude about the document format 2. `DOCUMENT_SCHEMA` is passed as a tool's input schema 3. `tool_choice` forces Claude to call the tool (guaranteeing structured output) 4. The tool use block's `input` contains the document data 5. Pass it directly to `docs.save()` # Any LLM Source: https://surfacedocs.mintlify.app/providers/any-llm Use SYSTEM_PROMPT with any model that supports text output # Any LLM For models without native structured output support, use `SYSTEM_PROMPT` to instruct the model to return JSON in the correct format. ```python theme={null} from surfacedocs import SurfaceDocs, SYSTEM_PROMPT docs = SurfaceDocs() # Use SYSTEM_PROMPT with any LLM that accepts a system message messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": "Write documentation for user authentication"}, ] # Call your LLM however you normally would response = your_llm_client.chat(messages) # The LLM will return JSON matching the document schema result = docs.save(response.text) print(f"Saved: {result.url}") ``` ## What SYSTEM\_PROMPT contains The prompt instructs the LLM to: 1. Output a JSON object with `title` and `blocks` 2. Use the correct block types (`heading`, `paragraph`, `code`, `list`, `quote`, `table`, `image`, `divider`) 3. Include proper metadata (e.g., `level` for headings, `language` for code blocks) 4. Use markdown formatting within text content ## Manual documents You can also skip the LLM entirely and build documents programmatically: ```python theme={null} from surfacedocs import SurfaceDocs docs = SurfaceDocs() result = docs.save_raw( title="Meeting Notes", blocks=[ {"type": "heading", "content": "Action Items", "metadata": {"level": 1}}, {"type": "list", "content": "- Review PR #123\n- Update docs", "metadata": {"listType": "bullet"}}, {"type": "divider", "content": ""}, {"type": "paragraph", "content": "Next meeting: Monday 10am"}, ], metadata={"source": "meeting-bot"}, ) print(f"Saved: {result.url}") ``` # Google Gemini Source: https://surfacedocs.mintlify.app/providers/gemini Generate documents with Gemini's structured output # Google Gemini Gemini supports structured output via `response_schema`. Use `GEMINI_DOCUMENT_SCHEMA` which omits `additionalProperties` (unsupported by Gemini). ```python theme={null} from surfacedocs import SurfaceDocs, GEMINI_DOCUMENT_SCHEMA, SYSTEM_PROMPT import google.generativeai as genai genai.configure(api_key="...") docs = SurfaceDocs() model = genai.GenerativeModel( model_name="gemini-2.0-flash", system_instruction=SYSTEM_PROMPT, generation_config=genai.GenerationConfig( response_mime_type="application/json", response_schema=GEMINI_DOCUMENT_SCHEMA, ), ) response = model.generate_content("Write documentation for user authentication") result = docs.save(response.text) print(f"Saved: {result.url}") ``` `GEMINI_DOCUMENT_SCHEMA` is specifically designed for Gemini — it removes `additionalProperties` fields that Gemini doesn't support and uses explicit metadata property definitions. ## Why a separate schema? Gemini's structured output has different constraints than OpenAI: | Feature | OpenAI | Gemini | | ---------------------- | ---------------------------- | ------------- | | `additionalProperties` | Required for strict mode | Not supported | | Nullable types | `"type": ["string", "null"]` | Not needed | | All fields required | Yes (strict mode) | No | The `GEMINI_DOCUMENT_SCHEMA` handles these differences so you don't have to. # OpenAI Source: https://surfacedocs.mintlify.app/providers/openai Generate documents with OpenAI's structured output # OpenAI OpenAI supports native JSON schema enforcement via `response_format`. Use `OPENAI_DOCUMENT_SCHEMA` for strict mode compatibility, or `DOCUMENT_SCHEMA` for standard mode. ## With strict mode (recommended) ```python theme={null} from surfacedocs import SurfaceDocs, OPENAI_DOCUMENT_SCHEMA, SYSTEM_PROMPT from openai import OpenAI openai = OpenAI() docs = SurfaceDocs() response = openai.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": "Write documentation for user authentication"}, ], response_format={ "type": "json_schema", "json_schema": { "name": "surfacedocs_document", "strict": True, "schema": OPENAI_DOCUMENT_SCHEMA, }, }, ) result = docs.save(response.choices[0].message.content) print(f"Saved: {result.url}") ``` `OPENAI_DOCUMENT_SCHEMA` sets `additionalProperties: false` and marks all fields as required, which OpenAI's strict mode requires. Use `DOCUMENT_SCHEMA` if you don't need strict mode. ## With standard mode ```python theme={null} from surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT from openai import OpenAI openai = OpenAI() docs = SurfaceDocs() response = openai.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": "Document the REST API authentication flow"}, ], response_format={ "type": "json_schema", "json_schema": {"name": "document", "schema": DOCUMENT_SCHEMA}, }, ) result = docs.save(response.choices[0].message.content) print(f"Saved: {result.url}") ``` # Quickstart Source: https://surfacedocs.mintlify.app/quickstart Save your first AI-generated document in under 5 minutes # Quickstart ```bash theme={null} pip install surfacedocs ``` Sign up at [app.surfacedocs.dev](https://app.surfacedocs.dev) and create an API key from your dashboard. Keys start with `sd_live_`. You can pass it directly or set it as an environment variable: ```bash theme={null} export SURFACEDOCS_API_KEY=sd_live_... ``` Choose your LLM provider: ```python theme={null} from surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT from openai import OpenAI openai = OpenAI() docs = SurfaceDocs() response = openai.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": "Write documentation for user authentication"}, ], response_format={ "type": "json_schema", "json_schema": {"name": "document", "schema": DOCUMENT_SCHEMA}, }, ) result = docs.save(response.choices[0].message.content) print(f"Saved: {result.url}") ``` ```python theme={null} from surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT import google.generativeai as genai genai.configure(api_key="...") docs = SurfaceDocs() model = genai.GenerativeModel( model_name="gemini-2.0-flash", system_instruction=SYSTEM_PROMPT, generation_config=genai.GenerationConfig( response_mime_type="application/json", response_schema=DOCUMENT_SCHEMA, ), ) response = model.generate_content("Write documentation for user authentication") result = docs.save(response.text) print(f"Saved: {result.url}") ``` ```python theme={null} from surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT import anthropic client = anthropic.Anthropic() docs = SurfaceDocs() response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=4096, system=SYSTEM_PROMPT, messages=[ {"role": "user", "content": "Write documentation for user authentication"}, ], tools=[{ "name": "create_document", "description": "Create a structured document", "input_schema": DOCUMENT_SCHEMA, }], tool_choice={"type": "tool", "name": "create_document"}, ) tool_use = next(b for b in response.content if b.type == "tool_use") result = docs.save(tool_use.input) print(f"Saved: {result.url}") ``` Open the URL printed in the previous step. Your document is live and shareable at `https://app.surfacedocs.dev/d/...`. ## Next steps Explore all SDK methods and models. Detailed guides for each LLM provider. # Block Types Source: https://surfacedocs.mintlify.app/schema/block-types All supported block types and their metadata options # Block Types Documents are composed of blocks. Each block has a `type`, `content`, and optional `metadata`. ## heading Section header. **Always include `metadata.level`.** ```json theme={null} {"type": "heading", "content": "Getting Started", "metadata": {"level": 1}} {"type": "heading", "content": "Installation", "metadata": {"level": 2}} {"type": "heading", "content": "Prerequisites", "metadata": {"level": 3}} ``` | Metadata | Type | Required | Description | | -------- | ---- | -------- | ------------------------------------------------------------- | | `level` | int | Yes | Heading level, 1–6. Use proper hierarchy (don't skip levels). | ## paragraph Body text with inline markdown. ```json theme={null} {"type": "paragraph", "content": "Use **bold**, *italic*, `code`, and [links](https://example.com)."} ``` No metadata options. ## code Code block with optional syntax highlighting. ```json theme={null} {"type": "code", "content": "print('hello world')", "metadata": {"language": "python"}} ``` | Metadata | Type | Required | Description | | ---------- | ------ | -------- | ----------------------------------------------------------------------- | | `language` | string | No | Language for syntax highlighting (e.g., `python`, `javascript`, `bash`) | ## list Bullet or numbered list. Content uses markdown list syntax. ```json theme={null} {"type": "list", "content": "- Item one\n- Item two\n- Item three", "metadata": {"listType": "bullet"}} {"type": "list", "content": "1. First\n2. Second\n3. Third", "metadata": {"listType": "ordered"}} ``` | Metadata | Type | Required | Description | | ---------- | ------ | -------- | ------------------------- | | `listType` | string | No | `"bullet"` or `"ordered"` | ## quote Block quote. ```json theme={null} {"type": "quote", "content": "The best way to predict the future is to invent it."} ``` No metadata options. ## table Markdown-formatted table. ```json theme={null} {"type": "table", "content": "| Name | Role |\n|------|------|\n| Alice | Admin |\n| Bob | User |"} ``` No metadata options. ## image Image block. ```json theme={null} {"type": "image", "content": "", "metadata": {"url": "https://example.com/diagram.png", "alt": "System architecture"}} ``` | Metadata | Type | Required | Description | | -------- | ------ | -------- | ----------- | | `url` | string | Yes | Image URL | | `alt` | string | No | Alt text | ## divider Horizontal rule. Content should be an empty string. ```json theme={null} {"type": "divider", "content": ""} ``` No metadata options. ## Inline Markdown Text content in `paragraph`, `heading`, `list`, and `quote` blocks supports inline markdown: * `**bold**` → **bold** * `*italic*` → *italic* * `` `code` `` → `code` * `[link](url)` → [link](url) # Document Examples Source: https://surfacedocs.mintlify.app/schema/examples Full JSON examples of SurfaceDocs documents # Document Examples ## Minimal document ```json theme={null} { "title": "Hello World", "blocks": [ {"type": "paragraph", "content": "This is a minimal document."} ] } ``` ## Technical documentation ```json theme={null} { "title": "API Authentication Guide", "metadata": { "source": "docs-agent", "tags": ["api", "auth"] }, "blocks": [ {"type": "heading", "content": "Authentication", "metadata": {"level": 1}}, {"type": "paragraph", "content": "All API requests require a Bearer token in the Authorization header."}, {"type": "heading", "content": "Getting a Token", "metadata": {"level": 2}}, {"type": "paragraph", "content": "Send a POST request to `/auth/token` with your credentials:"}, {"type": "code", "content": "curl -X POST https://api.example.com/auth/token \\\n -H 'Content-Type: application/json' \\\n -d '{\"email\": \"user@example.com\", \"password\": \"...\"}'", "metadata": {"language": "bash"}}, {"type": "heading", "content": "Using the Token", "metadata": {"level": 2}}, {"type": "paragraph", "content": "Include the token in subsequent requests:"}, {"type": "code", "content": "curl https://api.example.com/users/me \\\n -H 'Authorization: Bearer eyJhbG...'", "metadata": {"language": "bash"}}, {"type": "heading", "content": "Error Codes", "metadata": {"level": 2}}, {"type": "table", "content": "| Code | Meaning |\n|------|---------|\n| 401 | Invalid or expired token |\n| 403 | Insufficient permissions |"}, {"type": "divider", "content": ""}, {"type": "quote", "content": "Tokens expire after 24 hours. Use the refresh endpoint to get a new one."} ] } ``` ## Report with mixed content ```json theme={null} { "title": "Weekly Performance Report", "metadata": { "source": "analytics-pipeline", "tags": ["report", "weekly"] }, "blocks": [ {"type": "heading", "content": "Summary", "metadata": {"level": 1}}, {"type": "paragraph", "content": "This report covers the period **Feb 3–9, 2025**."}, {"type": "heading", "content": "Key Metrics", "metadata": {"level": 2}}, {"type": "table", "content": "| Metric | This Week | Last Week | Change |\n|--------|-----------|-----------|--------|\n| Requests | 1.2M | 1.1M | +9% |\n| Latency (p99) | 120ms | 145ms | -17% |\n| Error Rate | 0.02% | 0.05% | -60% |"}, {"type": "heading", "content": "Highlights", "metadata": {"level": 2}}, {"type": "list", "content": "- Deployed new caching layer — p99 latency dropped 17%\n- Error rate at all-time low\n- Traffic growing steadily", "metadata": {"listType": "bullet"}}, {"type": "heading", "content": "Action Items", "metadata": {"level": 2}}, {"type": "list", "content": "1. Investigate remaining timeout errors\n2. Scale read replicas before next traffic spike\n3. Update alerting thresholds", "metadata": {"listType": "ordered"}} ] } ``` # Delete Documents Source: https://surfacedocs.mintlify.app/sdk/delete-documents Remove documents with delete_document() # Delete Documents ## delete\_document() Delete a document and all its blocks by ID. ```python theme={null} from surfacedocs import SurfaceDocs, DocumentNotFoundError docs = SurfaceDocs() docs.delete_document("doc_abc123") ``` **Parameters:** | Parameter | Type | Required | Description | | ------------- | ----- | -------- | ------------------------- | | `document_id` | `str` | Yes | The document ID to delete | **Returns:** `None` **Raises:** `DocumentNotFoundError` if the document doesn't exist. ## Example: save, read, delete ```python theme={null} from surfacedocs import SurfaceDocs docs = SurfaceDocs() # Save result = docs.save_raw( title="Temp Report", blocks=[{"type": "paragraph", "content": "This is temporary."}], ) # Read doc = docs.get_document(result.id) print(doc.title) # "Temp Report" # Delete docs.delete_document(result.id) ``` # Folders Source: https://surfacedocs.mintlify.app/sdk/folders Organize documents with create_folder() and list_folders() # Folders ## Create a folder ```python theme={null} from surfacedocs import SurfaceDocs docs = SurfaceDocs() # Create a root folder folder = docs.create_folder("Engineering") print(folder.id) # "fld_abc123" print(folder.name) # "Engineering" # Create a subfolder subfolder = docs.create_folder("Backend", parent_id=folder.id) ``` **Parameters:** | Parameter | Type | Required | Description | | ----------- | ----- | -------- | ---------------------------- | | `name` | `str` | Yes | Folder name | | `parent_id` | `str` | No | Parent folder ID for nesting | **Returns:** [`Folder`](#folder-model) **Raises:** `FolderNotFoundError` if `parent_id` doesn't exist. ## List folders ```python theme={null} # List all root folders folders = docs.list_folders() # List subfolders of a specific folder subfolders = docs.list_folders(parent_id="fld_abc123") for folder in folders: print(f"{folder.name} ({folder.path})") ``` **Parameters:** | Parameter | Type | Required | Description | | ----------- | ----- | -------- | ----------------------- | | `parent_id` | `str` | No | Filter by parent folder | **Returns:** `list[Folder]` ## Save to a folder Pass `folder_id` to `save()` or `save_raw()`: ```python theme={null} result = docs.save(content, folder_id=folder.id) result = docs.save_raw( title="Architecture Overview", blocks=[{"type": "paragraph", "content": "Our system uses..."}], folder_id=subfolder.id, ) ``` ## Folder model ```python theme={null} folder.id # "fld_abc123" folder.name # "Engineering" folder.parent_id # "fld_parent" or None folder.path # "/Engineering" folder.depth # 0 folder.created_at # "2024-01-01T00:00:00Z" ``` # SDK Overview Source: https://surfacedocs.mintlify.app/sdk/overview What's included in the surfacedocs Python package # Python SDK Overview The `surfacedocs` package provides everything you need to save LLM-generated documents. ```bash theme={null} pip install surfacedocs ``` ## Exports | Export | Type | Purpose | | ------------------------ | --------- | ------------------------------------------------------------------- | | `SurfaceDocs` | class | HTTP client for saving, reading, versioning, and managing documents | | `SYSTEM_PROMPT` | str | Instructions for LLMs to generate documents in the correct format | | `DOCUMENT_SCHEMA` | dict | JSON schema for LLM structured output (generic) | | `OPENAI_DOCUMENT_SCHEMA` | dict | Schema compatible with OpenAI strict mode | | `GEMINI_DOCUMENT_SCHEMA` | dict | Schema compatible with Google Gemini's `response_schema` | | `SearchResult` | dataclass | A document search result | | `VersionResult` | dataclass | Result from pushing or restoring a version | | `VersionSummary` | dataclass | Summary of a document version | ```python theme={null} from surfacedocs import ( SurfaceDocs, SYSTEM_PROMPT, DOCUMENT_SCHEMA, OPENAI_DOCUMENT_SCHEMA, GEMINI_DOCUMENT_SCHEMA, ) ``` ## Initialization ```python theme={null} from surfacedocs import SurfaceDocs # Pass API key directly client = SurfaceDocs(api_key="sd_live_...") # Or use the SURFACEDOCS_API_KEY environment variable client = SurfaceDocs() ``` ## Methods | Method | Description | | --------------------------------------------------------------------------------------- | ---------------------------------------- | | [`save(content, folder_id=None)`](/sdk/save-documents#save) | Save a document from LLM JSON output | | [`save_raw(title, blocks, ...)`](/sdk/save-documents#save_raw) | Save a document with explicit parameters | | [`get_document(document_id)`](/sdk/read-documents) | Retrieve a document by ID | | [`search_documents(query, tag, ...)`](/sdk/search-documents) | Search documents by title or tag | | [`delete_document(document_id)`](/sdk/delete-documents) | Delete a document by ID | | [`create_folder(name, parent_id=None)`](/sdk/folders#create-a-folder) | Create a new folder | | [`list_folders(parent_id=None)`](/sdk/folders#list-folders) | List folders | | [`push_version(document_id, content)`](/sdk/versioning#push_version) | Push a new version from LLM output | | [`push_version_raw(document_id, title, blocks, ...)`](/sdk/versioning#push_version_raw) | Push a version with explicit parameters | | [`list_versions(document_id)`](/sdk/versioning#list_versions) | List all versions of a document | | [`get_version(document_id, version)`](/sdk/versioning#get_version) | Get a specific version with blocks | | [`restore_version(document_id, version)`](/sdk/versioning#restore_version) | Restore a previous version as latest | ## Error Handling ```python theme={null} from surfacedocs import ( SurfaceDocsError, # Base error AuthenticationError, # Invalid API key ValidationError, # Invalid document format DocumentNotFoundError, # Document doesn't exist FolderNotFoundError, # Folder doesn't exist VersionNotFoundError, # Version doesn't exist ) ``` ## Environment Variables | Variable | Description | | --------------------- | ----------------------------------------------- | | `SURFACEDOCS_API_KEY` | API key (alternative to passing in constructor) | # Read Documents Source: https://surfacedocs.mintlify.app/sdk/read-documents Retrieve documents with get_document() # Read Documents ## get\_document() Retrieve a document and its blocks by ID. ```python theme={null} from surfacedocs import SurfaceDocs docs = SurfaceDocs() doc = docs.get_document("doc_abc123") print(doc.title) print(doc.url) for block in doc.blocks: print(f"[{block.type}] {block.content[:50]}") ``` **Parameters:** | Parameter | Type | Required | Description | | ------------- | ----- | -------- | --------------------------- | | `document_id` | `str` | Yes | The document ID to retrieve | **Returns:** [`Document`](#document) **Raises:** `DocumentNotFoundError` if the document doesn't exist. ## Document ```python theme={null} doc.id # "doc_abc123" doc.url # "https://app.surfacedocs.dev/d/doc_abc123" doc.folder_id # "fld_xyz" doc.title # "My Document" doc.content_type # "markdown" doc.visibility # "private" doc.blocks # list[Block] doc.metadata # dict or None doc.created_at # "2024-01-01T00:00:00Z" doc.updated_at # "2024-01-02T00:00:00Z" ``` ## Block Each document contains a list of `Block` objects: ```python theme={null} block.id # "blk_abc123" block.order # 0 block.type # "heading", "paragraph", "code", etc. block.content # "Hello world" block.metadata # {"level": 1} or None ``` # Save Documents Source: https://surfacedocs.mintlify.app/sdk/save-documents Save LLM output as hosted documents with save() and save_raw() # Save Documents ## save() Save a document from LLM output. Accepts a JSON string or a dict. ```python theme={null} from surfacedocs import SurfaceDocs docs = SurfaceDocs() # From a JSON string (e.g., direct LLM output) result = docs.save(response.choices[0].message.content) # From a dict result = docs.save({ "title": "My Document", "blocks": [{"type": "paragraph", "content": "Hello world"}] }) # Save to a specific folder result = docs.save(content, folder_id="fld_abc123") ``` **Parameters:** | Parameter | Type | Required | Description | | ----------- | --------------- | -------- | ---------------------------------------------- | | `content` | `str` or `dict` | Yes | JSON string or dict with `title` and `blocks` | | `folder_id` | `str` | No | Target folder ID. Uses root folder if omitted. | **Returns:** [`SaveResult`](#saveresult) ## save\_raw() Save a document with explicit parameters instead of parsing LLM output. ```python theme={null} result = docs.save_raw( title="API Documentation", blocks=[ {"type": "heading", "content": "Authentication", "metadata": {"level": 1}}, {"type": "paragraph", "content": "Use Bearer tokens for auth."}, {"type": "code", "content": "curl -H 'Authorization: Bearer ...'", "metadata": {"language": "bash"}}, ], metadata={"source": "doc-generator", "version": "1.0"}, ) ``` **Parameters:** | Parameter | Type | Required | Description | | ----------- | ------------ | -------- | -------------------------------------------- | | `title` | `str` | Yes | Document title | | `blocks` | `list[dict]` | Yes | List of content blocks | | `folder_id` | `str` | No | Target folder ID | | `metadata` | `dict` | No | Document-level metadata (source, tags, etc.) | **Returns:** [`SaveResult`](#saveresult) ## SaveResult Both `save()` and `save_raw()` return a `SaveResult`: ```python theme={null} result.id # "doc_abc123" result.url # "https://app.surfacedocs.dev/d/doc_abc123" result.folder_id # "fld_xyz" ``` # Search Documents Source: https://surfacedocs.mintlify.app/sdk/search-documents Find documents by title or tag with search_documents() # Search Documents ## search\_documents() Search your documents by title prefix and/or tag. Results are scoped to the authenticated user. ```python theme={null} from surfacedocs import SurfaceDocs docs = SurfaceDocs() # Search by title prefix results = docs.search_documents(query="API") # Search by tag results = docs.search_documents(tag="guide") # Combine title + tag results = docs.search_documents(query="Auth", tag="security") # Scope to a folder results = docs.search_documents(query="setup", folder_id="fld_abc123") for result in results: print(f"{result.title} → {result.url}") ``` **Parameters:** | Parameter | Type | Required | Description | | ----------- | ----- | -------- | --------------------------------------------- | | `query` | `str` | No\* | Title prefix to search for (case-insensitive) | | `tag` | `str` | No\* | Exact tag to match | | `folder_id` | `str` | No | Folder ID to scope results | | `limit` | `int` | No | Max results to return (default: 20, max: 50) | | `offset` | `int` | No | Number of results to skip for pagination | At least one of `query` or `tag` is required. **Returns:** `list[SearchResult]` **Raises:** `ValidationError` if neither `query` nor `tag` is provided. ## SearchResult Each result contains document metadata (no blocks — use [`get_document()`](/sdk/read-documents) to fetch full content). ```python theme={null} result.id # "doc_abc123" result.url # "https://app.surfacedocs.dev/d/doc_abc123" result.folder_id # "fld_xyz" result.title # "API Documentation" result.content_type # "markdown" result.block_count # 15 result.visibility # "private" result.metadata # {"tags": ["api", "guide"], "source": "claude"} result.current_version # 3 result.created_at # "2024-01-01T00:00:00Z" result.updated_at # "2024-01-02T00:00:00Z" ``` ## Pagination Use `limit` and `offset` for paginating through results: ```python theme={null} # First page page1 = docs.search_documents(query="report", limit=10, offset=0) # Second page page2 = docs.search_documents(query="report", limit=10, offset=10) ``` ## Examples ### Find all documents tagged by source ```python theme={null} # Find everything generated by Claude claude_docs = docs.search_documents(tag="claude") # Find everything from a specific pipeline pipeline_docs = docs.search_documents(tag="weekly-report") ``` ### Search within a project folder ```python theme={null} results = docs.search_documents( query="architecture", folder_id="fld_project_x", limit=5, ) ``` # Versioning Source: https://surfacedocs.mintlify.app/sdk/versioning Push, list, and restore document versions with the Python SDK # Versioning SurfaceDocs supports document versioning — push new versions to the same document, view history, and restore previous versions. This is ideal for recurring reports, living documents, and any pipeline that updates the same document over time. ## push\_version() Push a new version from LLM output. Accepts a JSON string or dict, just like `save()`. ```python theme={null} from surfacedocs import SurfaceDocs docs = SurfaceDocs() # First, create the document result = docs.save(initial_output) document_id = result.id # Later, push a new version version = docs.push_version(document_id, updated_output) print(version.version) # 2 print(version.version_count) # 2 print(version.url) # same URL, new content ``` **Parameters:** | Parameter | Type | Required | Description | | ------------- | --------------- | -------- | --------------------------------------------- | | `document_id` | `str` | Yes | The document to version | | `content` | `str` or `dict` | Yes | JSON string or dict with `title` and `blocks` | **Returns:** [`VersionResult`](#versionresult) ## push\_version\_raw() Push a new version with explicit parameters. ```python theme={null} version = docs.push_version_raw( document_id="doc_abc123", title="Daily Sales Report — Feb 14", blocks=[ {"type": "heading", "content": "Sales Summary", "metadata": {"level": 1}}, {"type": "paragraph", "content": "Total revenue: $42,000"}, ], metadata={"generated_at": "2026-02-14", "source": "sales-pipeline"}, ) ``` **Parameters:** | Parameter | Type | Required | Description | | ------------- | ------------ | -------- | ----------------------- | | `document_id` | `str` | Yes | The document to version | | `title` | `str` | Yes | Document title | | `blocks` | `list[dict]` | Yes | List of content blocks | | `metadata` | `dict` | No | Document-level metadata | **Returns:** [`VersionResult`](#versionresult) ## list\_versions() List all versions of a document. ```python theme={null} versions = docs.list_versions("doc_abc123") for v in versions: print(f"v{v.version}: {v.title} ({v.block_count} blocks, {v.created_at})") ``` **Parameters:** | Parameter | Type | Required | Description | | ------------- | ----- | -------- | --------------- | | `document_id` | `str` | Yes | The document ID | **Returns:** `list[VersionSummary]` ## get\_version() Get a specific version with its full content and blocks. ```python theme={null} doc = docs.get_version("doc_abc123", version=1) print(doc.title) for block in doc.blocks: print(f" [{block.type}] {block.content[:50]}") ``` **Parameters:** | Parameter | Type | Required | Description | | ------------- | ----- | -------- | -------------------------- | | `document_id` | `str` | Yes | The document ID | | `version` | `int` | Yes | Version number to retrieve | **Returns:** [`Document`](/sdk/read-documents) — the document at that version **Raises:** `VersionNotFoundError` if the version doesn't exist. ## restore\_version() Restore a previous version as the new latest. This creates a new version with the content from the specified version. ```python theme={null} result = docs.restore_version("doc_abc123", version=1) print(result.version) # 4 (new version number) print(result.version_count) # 4 ``` **Parameters:** | Parameter | Type | Required | Description | | ------------- | ----- | -------- | ------------------------- | | `document_id` | `str` | Yes | The document ID | | `version` | `int` | Yes | Version number to restore | **Returns:** [`VersionResult`](#versionresult) **Raises:** `VersionNotFoundError` if the version doesn't exist. ## VersionResult Returned by `push_version()`, `push_version_raw()`, and `restore_version()`: ```python theme={null} result.id # "doc_abc123" result.url # "https://app.surfacedocs.dev/d/doc_abc123" result.version # 3 result.version_count # 3 ``` ## VersionSummary Returned by `list_versions()`: ```python theme={null} summary.version # 1 summary.title # "Daily Report — Feb 13" summary.block_count # 5 summary.created_at # "2026-02-13T10:00:00Z" ``` ## Examples ### Daily report pipeline A pipeline that updates the same document every day: ```python theme={null} from surfacedocs import SurfaceDocs, SYSTEM_PROMPT docs = SurfaceDocs() REPORT_DOC_ID = "doc_daily_report" def run_daily_report(llm_output: str): """Push today's report as a new version.""" result = docs.push_version(REPORT_DOC_ID, llm_output) print(f"Published v{result.version}: {result.url}") return result ``` ### Checking version history ```python theme={null} versions = docs.list_versions("doc_abc123") print(f"{len(versions)} versions found") for v in versions: print(f" v{v.version}: {v.title} — {v.created_at}") ``` ### Restoring a previous version ```python theme={null} # Something went wrong with v3, restore v2 result = docs.restore_version("doc_abc123", version=2) print(f"Restored to v{result.version}") # v4 (copy of v2's content) ```