Save LLM-generated documents to SurfaceDocs.
pip install surfacedocsfrom surfacedocs import SurfaceDocs, DOCUMENT_SCHEMA, SYSTEM_PROMPT
from openai import OpenAI
# Initialize clients
openai = OpenAI()
docs = SurfaceDocs(api_key="sd_live_...")
# Generate a document with your LLM
response = openai.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "Document our REST API authentication flow"},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "surfacedocs_document",
"schema": DOCUMENT_SCHEMA,
},
},
)
# Save to SurfaceDocs
result = docs.save(response.choices[0].message.content)
print(result.url) # https://app.surfacedocs.dev/d/abc123The SDK provides three exports:
| Export | Type | Purpose |
|---|---|---|
DOCUMENT_SCHEMA |
dict | JSON schema for LLM structured output |
SYSTEM_PROMPT |
str | Instructions for LLM to generate documents |
SurfaceDocs |
class | HTTP client to save documents |
from surfacedocs import SurfaceDocs
# Initialize with API key
client = SurfaceDocs(api_key="sd_live_...")
# Or use environment variable
# export SURFACEDOCS_API_KEY=sd_live_...
client = SurfaceDocs()Save a document from LLM output.
# From JSON string
result = client.save(response.choices[0].message.content)
# From dict
result = client.save({
"title": "My Document",
"blocks": [{"type": "paragraph", "content": "Hello world"}]
})
# To specific folder
result = client.save(content, folder_id="folder_abc123")Save a document with explicit parameters.
result = client.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"},
)Retrieve a document by ID.
doc = client.get_document("doc_abc123")
print(doc.title) # "API Documentation"
print(doc.blocks[0].type) # "heading"
print(doc.blocks[0].content) # "Authentication"Delete a document by ID.
client.delete_document("doc_abc123")Create a new folder.
folder = client.create_folder("API Docs")
print(folder.id) # "fld_abc123"
print(folder.name) # "API Docs"
# Create a subfolder
subfolder = client.create_folder("v2", parent_id=folder.id)List folders, optionally filtered by parent.
# List all root folders
folders = client.list_folders()
# List subfolders of a specific folder
subfolders = client.list_folders(parent_id="fld_abc123")Both save() and save_raw() return a SaveResult:
result.id # "doc_abc123"
result.url # "https://app.surfacedocs.dev/d/doc_abc123"
result.folder_id # "folder_xyz"Returned by get_document():
doc.id # "doc_abc123"
doc.url # "https://app.surfacedocs.dev/d/doc_abc123"
doc.folder_id # "folder_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"Each document contains a list of Block objects:
block.id # "blk_abc123"
block.order # 0
block.type # "heading", "paragraph", "code", etc.
block.content # "Hello world"
block.metadata # {"level": 1} or NoneReturned by create_folder() and list_folders():
folder.id # "fld_abc123"
folder.name # "API Docs"
folder.parent_id # "fld_parent" or None
folder.path # "/API Docs"
folder.depth # 0
folder.created_at # "2024-01-01T00:00:00Z"JSON schema dict for LLM structured output. Pass directly to your LLM provider.
System prompt string to instruct LLMs on document format.
from surfacedocs import SYSTEM_PROMPT
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": "Document the login flow"},
]Documents are composed of blocks:
| Type | Description | Metadata |
|---|---|---|
heading |
Section header | level (1-6) |
paragraph |
Body text | - |
code |
Code block | language (optional) |
list |
Bullet/numbered list | listType ("bullet" or "ordered") |
quote |
Block quote | - |
table |
Markdown table | - |
image |
Image | url (required), alt (optional) |
divider |
Horizontal rule | - |
Text content supports inline markdown: **bold**, *italic*, `code`, [link](url)
from surfacedocs import (
SurfaceDocs,
SurfaceDocsError,
AuthenticationError,
DocumentNotFoundError,
FolderNotFoundError,
ValidationError,
)
try:
result = client.save(content)
except AuthenticationError:
print("Invalid API key")
except ValidationError as e:
print(f"Invalid document: {e}")
except SurfaceDocsError as e:
print(f"API error: {e}")
try:
doc = client.get_document("doc_abc123")
except DocumentNotFoundError:
print("Document does not exist")
try:
folder = client.create_folder("Docs", parent_id="fld_nonexistent")
except FolderNotFoundError:
print("Parent folder does not exist")# API key (alternative to passing in code)
export SURFACEDOCS_API_KEY=sd_live_...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}")Using Claude's structured outputs with tool use:
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}")Using Gemini's structured output with JSON schema:
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}")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"},
)from surfacedocs import SurfaceDocs, DocumentNotFoundError
docs = SurfaceDocs()
# Save a document
result = docs.save_raw(
title="API Guide",
blocks=[{"type": "paragraph", "content": "Welcome to the API."}],
)
# Retrieve it
doc = docs.get_document(result.id)
print(doc.title) # "API Guide"
# Delete it
docs.delete_document(result.id)from surfacedocs import SurfaceDocs
docs = SurfaceDocs()
# Create a folder hierarchy
parent = docs.create_folder("Engineering")
child = docs.create_folder("Backend", parent_id=parent.id)
# List root folders
for folder in docs.list_folders():
print(folder.name)
# List subfolders
for folder in docs.list_folders(parent_id=parent.id):
print(f" {folder.name}")
# Save a document to a folder
result = docs.save_raw(
title="Architecture Overview",
blocks=[{"type": "paragraph", "content": "Our system uses..."}],
folder_id=child.id,
)MIT