Artifacts Endpoints¶
Store and retrieve user-created content (diagrams, notes, code) with path-based organization.
Save Artifact¶
Save an artifact to storage.
Headers¶
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer YOUR_JWT_TOKEN |
Content-Type |
Yes | application/json |
Request Body¶
{
"path": "/project/docs/api-notes.md",
"content": "# API Notes\n\nImportant information about the API..."
}
Request Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Full path including filename |
content |
string | Yes | File content to save |
Path Requirements¶
- Must start with
/ - Must specify a filename (not just a directory)
- Maximum 10 levels deep
- Valid characters: alphanumeric, dash, underscore, dot, slash
- No
..(path traversal) - No hidden files (starting with
.) - No double slashes
Valid Path Examples¶
Invalid Path Examples¶
readme.md # Missing leading /
/ # Directory, not file
/project/ # Directory, not file
/../secrets.txt # Path traversal
/.hidden # Hidden file
//double.txt # Double slash
Response¶
{
"status": "success",
"data": {
"success": true,
"message": "Artifact saved successfully",
"path": "/project/docs/api-notes.md",
"artifact_id": "550e8400-e29b-41d4-a716-446655440000",
"content_type": "text/markdown",
"size": 1024,
"storage_path": "artifacts/project/docs/api-notes.md"
},
"meta": {...}
}
Response Fields¶
| Field | Description |
|---|---|
success |
Boolean indicating operation success |
message |
Human-readable status message |
path |
The artifact path |
artifact_id |
Unique identifier for the artifact |
content_type |
Detected MIME type |
size |
Content size in bytes |
storage_path |
Internal storage location |
Size Limits¶
- Maximum artifact size: 1MB
Errors¶
| Status | Code | Description |
|---|---|---|
| 400 | - | Invalid path format |
| 409 | - | Artifact already exists at path |
| 413 | - | Content exceeds 1MB limit |
Example¶
curl -X POST https://api.penfield.app/api/v2/artifacts \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"path": "/notes/python-tips.md",
"content": "# Python Tips\n\n- Use list comprehensions\n- Prefer generators for large data"
}'
Retrieve Artifact¶
Retrieve an artifact's content.
Query Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Full path of the artifact |
Response¶
{
"status": "success",
"data": {
"success": true,
"content": "# Python Tips\n\n- Use list comprehensions\n- Prefer generators for large data",
"path": "/notes/python-tips.md",
"content_type": "text/markdown",
"size": 78
},
"meta": {...}
}
Note: Due to a known issue,
.mdfiles may currently returntext/plaininstead oftext/markdown. See the known issue note below.
Response Fields¶
| Field | Description |
|---|---|
success |
Boolean indicating operation success |
content |
The artifact content |
path |
The artifact path |
content_type |
MIME type of the content (see known issue below) |
size |
Content size in bytes |
Known Issue: Some file extensions (including
.md) may return an incorrectcontent_typeon retrieve. For example,.mdfiles may returntext/plaininstead oftext/markdown. Use the file extension from thepathfield as a reliable alternative until this is resolved.
Errors¶
| Status | Code | Description |
|---|---|---|
| 400 | - | Invalid path format |
| 404 | - | Artifact not found |
Example¶
curl -X GET "https://api.penfield.app/api/v2/artifacts?path=/notes/python-tips.md" \
-H "Authorization: Bearer $JWT_TOKEN"
List Artifacts¶
List artifacts in a directory.
Query Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
prefix |
string | / |
Directory path to list |
name_pattern |
string | - | Case-insensitive substring filter applied to file and folder names |
Response¶
{
"status": "success",
"data": {
"success": true,
"path": "/project",
"folders": ["docs", "diagrams"],
"files": [
{
"name": "readme.md",
"size": 2048,
"content_type": "text/markdown",
"last_modified": "2025-01-06T12:00:00.000Z"
},
{
"name": "notes.txt",
"size": 512,
"content_type": "text/plain",
"last_modified": "2025-01-05T10:00:00.000Z"
}
]
},
"meta": {...}
}
Response Fields¶
| Field | Description |
|---|---|
path |
The directory path being listed |
folders |
Array of folder names (strings) |
files[].name |
File name |
files[].size |
File size in bytes |
files[].content_type |
MIME type |
files[].last_modified |
Last modification timestamp |
Notes¶
- Returns immediate contents only (not recursive)
- Folders are returned as name strings; combine with
pathto get full paths - Empty directories are not shown
name_patternis applied server-side against both file names and folder names within the listed directory
Example¶
# List root directory
curl -X GET "https://api.penfield.app/api/v2/artifacts/list" \
-H "Authorization: Bearer $JWT_TOKEN"
# List specific directory
curl -X GET "https://api.penfield.app/api/v2/artifacts/list?prefix=/project/docs" \
-H "Authorization: Bearer $JWT_TOKEN"
# Filter by name (case-insensitive substring)
curl -X GET "https://api.penfield.app/api/v2/artifacts/list?prefix=/project/docs&name_pattern=meeting" \
-H "Authorization: Bearer $JWT_TOKEN"
Delete Artifact¶
Permanently delete an artifact.
Query Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Full path of the artifact |
Response¶
{
"status": "success",
"data": {
"message": "Artifact deleted: /notes/python-tips.md"
},
"meta": {...}
}
Notes¶
- This operation is permanent and cannot be undone
- Any memories you created that reference this artifact path will not be affected — delete those separately if needed
- Parent directories are not deleted (even if empty)
Errors¶
| Status | Code | Description |
|---|---|---|
| 400 | - | Invalid path format |
| 404 | - | Artifact not found |
Content Type Detection¶
Content types are automatically detected based on file extension:
| Extension | Content Type |
|---|---|
.md |
text/markdown |
.txt |
text/plain |
.json |
application/json |
.html |
text/html |
.css |
text/css |
.js |
application/javascript |
.py |
text/x-python |
.svg |
image/svg+xml |
.xml |
application/xml |
.yaml, .yml |
application/x-yaml |
.csv |
text/csv |
| Other | application/octet-stream |
Known Issue: Some file extensions (including
.md) may return an incorrectcontent_typewhen retrieving artifacts. The table above shows the intended behavior. As a workaround, use the file extension from thepathfield to determine the file type.
Use Cases¶
Documentation Storage¶
{
"path": "/docs/api/authentication.md",
"content": "# Authentication\n\nAPI authentication uses JWT tokens..."
}
Code Snippets¶
{
"path": "/snippets/python/async-example.py",
"content": "import asyncio\n\nasync def main():\n await asyncio.sleep(1)"
}
Diagrams (SVG)¶
{
"path": "/diagrams/architecture.svg",
"content": "<svg xmlns=\"http://www.w3.org/2000/svg\">...</svg>"
}
Configuration Files¶
Artifacts vs Memories¶
| Feature | Artifacts | Memories |
|---|---|---|
| Organization | Path-based hierarchy | Flat with tags |
| Content | Files (text, code, diagrams) | Knowledge snippets |
| Searchable | No | Yes (hybrid search) |
| Relationships | No | Yes (knowledge graph) |
| Use Case | Store files and full-length content | Store searchable knowledge |
Artifacts are files. They are not included in search or recall results. To retrieve an artifact, you need its path.
Finding Artifacts¶
There are three ways to find an artifact:
- By path — If you know the path, call Retrieve Artifact directly.
- By browsing — Use List Artifacts to browse the directory tree.
- Via a memory reference — Create a memory that includes the artifact path in its content. When that memory appears in search results, follow the path to retrieve the full artifact.
This third approach is useful for content that exceeds the memory size limit but still needs to be discoverable. For example, a long research document can be stored as an artifact, with a shorter summary memory that references the artifact path:
# 1. Store the full document as an artifact
curl -X POST https://api.penfield.app/api/v2/artifacts \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"path": "/research/sec-analysis.md",
"content": "# Full 50-page SEC analysis document content..."
}'
# 2. Create a searchable summary memory that points to it
curl -X POST https://api.penfield.app/api/v2/memories \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"content": "Regulatory analysis of silver bullion P2P trading under SEC guidelines. Covers exemptions, reporting requirements, and state-level variations. Full document: /research/sec-analysis.md",
"memory_type": "reference",
"tags": ["sec", "silver", "regulations"]
}'
When an agent searches for "SEC silver trading regulations", the summary memory appears in results. The agent reads the path from the content and calls retrieve_artifact to get the full document.