Skip to main content

Overview

The Artifacts API provides endpoints to retrieve files generated by AI agents or uploaded by users. It supports various file types including text, HTML, binary files, and files within .skill archives.

Get Artifact File

GET /api/threads/{thread_id}/artifacts/{path}

Retrieve an artifact file generated by the AI agent or uploaded by the user

Path Parameters

thread_id
string
required
The thread ID
path
string
required
The artifact path with virtual prefix (e.g., mnt/user-data/outputs/file.txt)

Query Parameters

download
boolean
default:"false"
If true, returns file as attachment for download. Otherwise, files are displayed inline.

Response

The response content type depends on the file type:
  • HTML files (.html): Rendered as HTML (text/html)
  • Text files: Returned as plain text with appropriate MIME type
  • Binary files: Returned with appropriate MIME type

Example Requests

View HTML File

curl http://localhost:8001/api/threads/abc123/artifacts/mnt/user-data/outputs/index.html
Response: HTML content rendered in browser

View Text File

curl http://localhost:8001/api/threads/abc123/artifacts/mnt/user-data/outputs/script.py
Response: Plain text with text/x-python MIME type

Download File

curl "http://localhost:8001/api/threads/abc123/artifacts/mnt/user-data/outputs/data.csv?download=true" \
  -o data.csv
Response: File downloaded with Content-Disposition: attachment header

View Uploaded File

curl http://localhost:8001/api/threads/abc123/artifacts/mnt/user-data/uploads/document.pdf
Response: PDF displayed inline in browser

Error Responses

400
Bad Request
Path is not a file
{
  "detail": "Path is not a file: mnt/user-data/outputs"
}
403
Forbidden
Access denied (path traversal attempt)
{
  "detail": "Access denied"
}
404
Not Found
Artifact not found
{
  "detail": "Artifact not found: mnt/user-data/outputs/missing.txt"
}

.skill Archive Files

GET /api/threads/{thread_id}/artifacts/{skill_path}.skill/{internal_path}

Retrieve a file from within a .skill archive without extracting

Path Format

To access files inside .skill archives, use the format:
/api/threads/{thread_id}/artifacts/{path/to/file}.skill/{internal_path}

Example Requests

View SKILL.md from Archive

curl http://localhost:8001/api/threads/abc123/artifacts/mnt/user-data/outputs/my-skill.skill/SKILL.md
Response: Markdown content from inside the ZIP archive

View Script from Archive

curl http://localhost:8001/api/threads/abc123/artifacts/mnt/user-data/outputs/my-skill.skill/scripts/install.sh
Response: Script content from scripts/install.sh inside the archive

How It Works

  1. The API detects .skill/ in the path
  2. Splits the path into archive path and internal path
  3. Opens the .skill file (ZIP archive)
  4. Extracts the requested file without extracting the entire archive
  5. Returns the file content with appropriate MIME type
  6. Caches for 5 minutes to avoid repeated ZIP extraction

Error Responses

404
Not Found
Archive or file not found
{
  "detail": "File 'README.md' not found in skill archive"
}

Virtual Paths

DeerFlow uses virtual paths to organize files:

Outputs Directory

Files generated by AI agents:
/mnt/user-data/outputs/
Example paths:
  • mnt/user-data/outputs/index.html - Generated web page
  • mnt/user-data/outputs/script.py - Generated Python script
  • mnt/user-data/outputs/my-skill.skill - Packaged skill file

Uploads Directory

Files uploaded by users:
/mnt/user-data/uploads/
Example paths:
  • mnt/user-data/uploads/document.pdf - Uploaded PDF
  • mnt/user-data/uploads/document.md - Auto-converted markdown
  • mnt/user-data/uploads/image.png - Uploaded image

Path Resolution

The API resolves virtual paths to actual filesystem paths:
mnt/user-data/outputs/file.txt
→ backend/sandbox/threads/{thread_id}/outputs/file.txt

mnt/user-data/uploads/file.pdf
→ backend/sandbox/threads/{thread_id}/uploads/file.pdf

Content Types

The API automatically detects and sets appropriate MIME types:

Text Files

  • .txttext/plain
  • .mdtext/markdown
  • .pytext/x-python
  • .jstext/javascript
  • .jsonapplication/json
  • .yaml, .ymlapplication/x-yaml

Web Files

  • .htmltext/html (rendered in browser)
  • .csstext/css
  • .jstext/javascript

Documents

  • .pdfapplication/pdf
  • .doc, .docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
  • .xls, .xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet

Images

  • .pngimage/png
  • .jpg, .jpegimage/jpeg
  • .gifimage/gif
  • .svgimage/svg+xml

Archives

  • .zipapplication/zip
  • .tar.gzapplication/gzip

Content Disposition

The API uses different Content-Disposition headers:

Inline Display (Default)

Content-Disposition: inline; filename*=UTF-8''document.pdf
Files are displayed in the browser when possible.

Force Download

With ?download=true:
Content-Disposition: attachment; filename*=UTF-8''document.pdf
Forces file download instead of inline display.

Security

Path Traversal Protection

The API validates all paths to prevent directory traversal attacks:
  • Paths must be within the thread’s sandbox directory
  • Attempts to access parent directories (../) are blocked
  • Returns 403 Forbidden for unauthorized access attempts

Filename Encoding

Filenames with special characters are properly encoded using RFC 5987:
Content-Disposition: inline; filename*=UTF-8''%E6%96%87%E4%BB%B6.txt
This ensures proper handling of:
  • Unicode characters
  • Spaces
  • Special characters

Use Cases

Display Generated HTML

<!-- Embed generated HTML page -->
<iframe 
  src="http://localhost:8001/api/threads/abc123/artifacts/mnt/user-data/outputs/index.html"
  width="100%" 
  height="600">
</iframe>

Download Generated File

async function downloadArtifact(threadId: string, path: string, filename: string) {
  const url = `http://localhost:8001/api/threads/${threadId}/artifacts/${path}?download=true`;
  
  const response = await fetch(url);
  const blob = await response.blob();
  
  // Create download link
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = filename;
  link.click();
}

// Usage
downloadArtifact('abc123', 'mnt/user-data/outputs/report.pdf', 'report.pdf');

View Artifact as Text

async function viewArtifactText(threadId: string, path: string) {
  const url = `http://localhost:8001/api/threads/${threadId}/artifacts/${path}`;
  const response = await fetch(url);
  const text = await response.text();
  return text;
}

// Usage
const code = await viewArtifactText('abc123', 'mnt/user-data/outputs/script.py');
console.log(code);

List Artifact URLs from Upload Response

// After uploading files
const uploadResponse = await uploadFiles(threadId, files);

// Get artifact URLs
const artifactUrls = uploadResponse.files.map(file => ({
  filename: file.filename,
  url: `http://localhost:8001${file.artifact_url}`,
  downloadUrl: `http://localhost:8001${file.artifact_url}?download=true`
}));

Preview File from .skill Archive

async function previewSkillFile(threadId: string, skillPath: string, internalPath: string) {
  const url = `http://localhost:8001/api/threads/${threadId}/artifacts/${skillPath}/${internalPath}`;
  const response = await fetch(url);
  const content = await response.text();
  return content;
}

// Usage
const skillMd = await previewSkillFile(
  'abc123',
  'mnt/user-data/outputs/my-skill.skill',
  'SKILL.md'
);

Uploads API

Upload files to threads

Gateway Overview

Learn about the Gateway API

Build docs developers (and LLMs) love