Artifact Resource
The Artifact resource provides file upload capabilities using presigned S3 URLs and webhook-based completion confirmation.
Overview
The artifact upload flow follows modern best practices:
- Request presigned URL from the backend
- Upload file directly to S3 using the presigned URL
- Webhook confirmation (automatic via S3 Lambda trigger)
- Query artifact metadata after upload completes
Architecture
Client → Magick Mind API (presign) → S3 (upload) → Lambda → Webhook → Artifact Service- Presigned URLs: Secure, time-limited upload URLs that keep file data off application servers
- Direct S3 Upload: Files stream directly to S3, making it scalable for large files
- Webhook Completion: S3 triggers Lambda which calls webhook to confirm upload
- Client Finalize: Fallback method for local development when webhooks aren't available
Quick Start
Simple Upload
from magick_mind import MagickMind
client = MagickMind(
email="user@example.com",
password="password",
base_url="https://api.example.com"
)
# Upload a file (all-in-one convenience method)
presign_resp, upload_resp = client.v1.artifact.upload_file(
file_path="./document.pdf",
content_type="application/pdf",
corpus_id="corpus-123"
)
print(f"Uploaded! Artifact ID: {presign_resp.id}")Manual Upload Flow
# Step 1: Get presigned URL
presign_resp = client.v1.artifact.presign_upload(
file_name="document.pdf",
content_type="application/pdf",
size_bytes=1024000,
corpus_id="corpus-123"
)
# Step 2: Upload to S3
with open("document.pdf", "rb") as f:
requests.put(
presign_resp.upload_url,
data=f,
headers=presign_resp.required_headers
)
# Step 3: Webhook handles completion automatically
# Or use finalize() in local dev:
client.v1.artifact.finalize(
artifact_id=presign_resp.id,
bucket=presign_resp.bucket,
key=presign_resp.key,
corpus_id="corpus-123"
)API Reference
presign_upload()
Get a presigned S3 URL for uploading a file.
Parameters:
file_name(str, required): Name of the file to uploadcontent_type(str, required): MIME type (e.g.,"application/pdf")size_bytes(int, required): File size in bytesend_user_id(str, optional): End user identifiercorpus_id(str, optional): Corpus to associate artifact with
Returns: PresignArtifactResponse
Example:
response = client.v1.artifact.presign_upload(
file_name="report.pdf",
content_type="application/pdf",
size_bytes=2048000
)upload_file()
Convenience method that combines presign and upload.
Parameters:
file_path(str, required): Path to file to uploadcontent_type(str, required): MIME typeend_user_id(str, optional): End user identifiercorpus_id(str, optional): Corpus to associate artifact with
Returns: tuple[PresignArtifactResponse, requests.Response]
Example:
presign_resp, upload_resp = client.v1.artifact.upload_file(
file_path="./data.json",
content_type="application/json",
corpus_id="corpus-456"
)get()
Retrieve artifact metadata by ID.
Parameters:
artifact_id(str, required): Artifact ID to retrieve
Returns: Artifact
Example:
artifact = client.v1.artifact.get(artifact_id="art-123")
print(f"Status: {artifact.status}")
print(f"S3 URL: {artifact.s3_url}")list()
Query artifacts with optional filters.
Parameters:
corpus_id(str, optional): Filter by corpusend_user_id(str, optional): Filter by end userstatus(str, optional): Filter by status (uploaded,processing,ready,failed)
Returns: list[Artifact]
Example:
# All artifacts for a corpus
artifacts = client.v1.artifact.list(corpus_id="corpus-123")
# Ready artifacts only
ready = client.v1.artifact.list(status="ready")delete()
Delete an artifact.
Parameters:
artifact_id(str, required): Artifact ID to delete
Returns: None
Example:
client.v1.artifact.delete(artifact_id="art-123")finalize()
Client-driven finalize (fallback for local development).
Parameters:
artifact_id(str, required): Artifact ID from presign responsebucket(str, required): S3 bucket namekey(str, required): S3 object keyversion_id(str, optional): S3 version IDsize_bytes(int, optional): Uploaded file sizecontent_type(str, optional): Content typeetag(str, optional): S3 ETagchecksum_sha256(str, optional): SHA256 checksumcorpus_id(str, optional): Corpus ID
Returns: FinalizeArtifactResponse
Example:
response = client.v1.artifact.finalize(
artifact_id=presign_resp.id,
bucket=presign_resp.bucket,
key=presign_resp.key,
corpus_id="corpus-123"
)Models
Artifact
Canonical artifact model representing an uploaded file.
Fields:
id(str): Unique artifact identifierbucket(str): S3 bucket namekey(str): S3 object keys3_url(str): S3 URL (s3://bucket/key)content_type(str): MIME typesize_bytes(int): File size in bytesstatus(str): Artifact statuscorpus_id(str, optional): Associated corpusend_user_id(str, optional): Uploader's end user IDcreated_at(int, optional): Creation timestampupdated_at(int, optional): Last update timestamp
Best Practices
1. Use upload_file() for Simplicity
The convenience method handles presigning and upload in one call:
presign_resp, upload_resp = client.v1.artifact.upload_file(
file_path="./file.pdf",
content_type="application/pdf"
)2. Check Upload Status
Always verify the S3 upload succeeded:
if upload_resp.status_code == 200:
print("Upload successful!")
else:
print(f"Upload failed: {upload_resp.status_code}")3. Use Webhooks in Production
In production, S3 Lambda webhooks handle completion automatically. Only use finalize() in local development.
4. Monitor Artifact Status
After upload, artifacts may be in different states:
artifact = client.v1.artifact.get(artifact_id=id)
if artifact.status == "ready":
# Artifact is ready for use
process_artifact(artifact.s3_url)
elif artifact.status == "processing":
# Still being processed (e.g., RAG ingestion)
print("Please wait...")
elif artifact.status == "failed":
# Upload or processing failed
print(f"Error: {artifact.error_code}")5. Associate with Corpus
Always associate artifacts with a corpus for organization:
presign_resp = client.v1.artifact.presign_upload(
file_name="doc.pdf",
content_type="application/pdf",
size_bytes=1000000,
corpus_id="corpus-123" # Associate with corpus
)Local Development
When running locally without S3 Lambda webhooks, use the finalize method:
# Upload file
presign_resp, _ = client.v1.artifact.upload_file(...)
# Manually finalize
client.v1.artifact.finalize(
artifact_id=presign_resp.id,
bucket=presign_resp.bucket,
key=presign_resp.key,
corpus_id="corpus-123"
)Error Handling
from requests.exceptions import HTTPError
try:
presign_resp, upload_resp = client.v1.artifact.upload_file(
file_path="./file.pdf",
content_type="application/pdf"
)
upload_resp.raise_for_status()
print(f"Success! Artifact ID: {presign_resp.id}")
except HTTPError as e:
if e.response.status_code == 401:
print("Authentication failed")
elif e.response.status_code == 413:
print("File too large")
else:
print(f"Upload failed: {e}")
except FileNotFoundError:
print("File not found")Chat Attachments
Artifacts can be attached to chat messages for document analysis, image understanding, and more.
Basic Flow
# Step 1: Upload artifact
presign_resp, upload_resp = client.v1.artifact.upload_file(
file_path="./financial_report.pdf",
content_type="application/pdf"
)
# Step 2: Send chat with artifact attached
chat_response = client.v1.chat.send(
api_key="sk-...",
mindspace_id="mind-123",
message="Summarize the key findings from this report",
enduser_id="user-456",
artifact_ids=[presign_resp.id] # Attach the artifact
)
print(chat_response.content.content) # AI summaryMultiple Attachments
# Upload multiple files
doc1_resp, _ = client.v1.artifact.upload_file(
file_path="./report1.pdf",
content_type="application/pdf"
)
doc2_resp, _ = client.v1.artifact.upload_file(
file_path="./report2.pdf",
content_type="application/pdf"
)
# Send chat with both attached
response = client.v1.chat.send(
api_key="sk-...",
mindspace_id="mind-123",
message="Compare these two reports and highlight differences",
enduser_id="user-456",
artifact_ids=[doc1_resp.id, doc2_resp.id]
)Reusing Artifacts
Artifacts are first-class resources that can be reused across multiple chats:
# Upload once
artifact_resp, _ = client.v1.artifact.upload_file(
file_path="./company_handbook.pdf",
content_type="application/pdf"
)
# Use in multiple chat messages
response1 = client.v1.chat.send(
message="What's the vacation policy?",
artifact_ids=[artifact_resp.id],
# ... other params
)
response2 = client.v1.chat.send(
message="What about remote work policies?",
artifact_ids=[artifact_resp.id], # Same artifact
# ... other params
)Benefits
- ✅ Efficient: Upload once, reference many times
- ✅ Scalable: Large files don't bloat message payloads
- ✅ Manageable: Artifacts have independent lifecycle (list, delete, etc.)
- ✅ Flexible: Mix different file types in one message
See Also
- ADR-004: Upload and Notifications
- Example Code