Upload flow
The Aion API uses a single one-shot request: send your file, get a download URL back. This page covers every detail of the upload, status polling, and download-url endpoints.
1. Upload a file
Send a POST to /v1/aion/upload with your file as multipart/form-data:
POST /v1/aion/upload
X-Aion-Key: YOUR_API_KEY
Content-Type: multipart/form-data
[file field: your file]Response (202)
{
"uploadId": "up_a1b2c3d4e5f6g7h8",
"status": "PENDING_UPLOAD",
"statusUrl": "/v1/upload/up_a1b2c3d4e5f6g7h8/status",
"downloadUrl": "https://payaion.com/d/abc123xyz789",
"expiresAt": "2026-02-22T14:35:00.000Z"
}The downloadUrl is a shareable link. The file is available for download as soon as it is stored on Walrus. Use the status endpoint to confirm the upload is ready.
1b. Upload from a URL
If your bot or webhook receives a temporary file URL (Discord, Slack, automation platforms), you can skip downloading the file yourself. Send the URL and Payaion fetches it for you:
POST /v1/aion/upload-url
X-Aion-Key: YOUR_API_KEY
Content-Type: application/json
{
"url": "https://cdn.example.com/photo123.jpg",
"fileName": "photo123.jpg"
}Response (202)
Same shape as a regular upload — you get uploadId, downloadUrl, and statusUrl.
Rules
- Only
https://URLs are accepted - Private IPs, localhost, and internal hostnames are blocked (SSRF protection)
- The fetch aborts as soon as the remote file exceeds your plan ceiling (500 MB on Basic, 1 GB on Pro), both on
Content-Lengthand mid-stream - Fetch timeout: 30 seconds →
408 fetch_timeout. An unreachable or erroring host returns502 fetch_failed - The
fileNamefield is optional — derived from the URL path if omitted
Code examples
curl
curl -X POST https://payaion-api.fly.dev/v1/aion/upload-url \
-H "X-Aion-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://cdn.example.com/photo123.jpg"}'JavaScript
const resp = await fetch('https://payaion-api.fly.dev/v1/aion/upload-url', {
method: 'POST',
headers: {
'X-Aion-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ url: fileUrl }),
});
const { downloadUrl } = await resp.json();Python
import requests
resp = requests.post(
"https://payaion-api.fly.dev/v1/aion/upload-url",
headers={"X-Aion-Key": API_KEY},
json={"url": file_url},
)
data = resp.json()
print("Share this URL:", data["downloadUrl"])Pricing & marketplace headers
Both upload endpoints accept optional headers for pricing and auto-listing:
| Header | Description |
|---|---|
X-Price-Per-Download | USD price per download (non-negative number). Omit for free files. |
X-Listing-Intent | JSON object with title, category, tags. Auto-lists on the marketplace once upload is READY. |
See Marketplace API for the full listing endpoint and validation rules.
Retry-safe uploads (Idempotency-Key)
Network failures happen. To safely retry an upload without creating duplicates, include an Idempotency-Key header:
POST /v1/aion/upload
X-Aion-Key: YOUR_API_KEY
Idempotency-Key: my-unique-request-id-abc123
Content-Type: multipart/form-data
[file field: your file]- Key must be 8–128 characters, alphanumeric, dashes, underscores, or dots
- If you retry with the same key, you get the original 202 response back
- Replayed responses include an
X-Idempotent-Replay: 1header - If the original request is still in progress, you get
409withRetry-After - Cached responses expire after 24 hours
Best practice: Always include an Idempotency-Key when your bot or script may retry on failure. Generate a unique key per intended upload (e.g. UUID or hash of the file name + timestamp).
2. Check upload status
After uploading, the file is stored on Walrus and available for download almost immediately. Poll the status endpoint to confirm:
GET /v1/upload/{uploadId}/status
X-Aion-Key: YOUR_API_KEYResponse (200)
{
"uploadId": "up_a1b2c3d4e5f6g7h8",
"status": "READY",
"ready": true,
"updatedAt": "2026-02-21T14:35:10.000Z"
}Possible status values:
PENDING_UPLOAD— Upload record created, worker handoff pendingUPLOADING— Upload worker is pushing file to storageREADY— File stored on Walrus network, available for downloadFAILED— Upload failedDELETING— File is being removed from the storage networkDELETED— File removed from both database and Walrus storage network
3. Get a fresh download URL
If you need a new share link (e.g. the original expired), request one:
POST /v1/upload/{uploadId}/download-url
X-Aion-Key: YOUR_API_KEY
Content-Type: application/jsonResponse (200)
{
"uploadId": "up_a1b2c3d4e5f6g7h8",
"downloadUrl": "https://payaion.com/d/abc123xyz789",
"expiresAt": "2026-02-21T14:37:10.000Z",
"ready": true
}4. What recipients see
When someone opens the downloadUrl, they see a download page with:
- File name and size
- Expiration date
- Download button (streams the file directly)
The page is the same design as manual uploads — no "uploaded by agent" badge or difference visible to recipients. The file is downloadable as soon as it is stored on Walrus. If the upload is still in progress, recipients will see a brief loading state that auto-refreshes.
Code examples
curl
curl -X POST https://payaion-api.fly.dev/v1/aion/upload \
-H "X-Aion-Key: YOUR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@document.pdf"JavaScript
const form = new FormData();
form.append('file', new Blob([fileBuffer]), 'document.pdf');
const resp = await fetch('https://payaion-api.fly.dev/v1/aion/upload', {
method: 'POST',
headers: {
'X-Aion-Key': API_KEY,
'Idempotency-Key': crypto.randomUUID(),
},
body: form,
});
const { uploadId, downloadUrl, expiresAt } = await resp.json();Python
import uuid, requests
resp = requests.post(
"https://payaion-api.fly.dev/v1/aion/upload",
headers={
"X-Aion-Key": API_KEY,
"Idempotency-Key": str(uuid.uuid4()),
},
files={"file": open("document.pdf", "rb")},
)
data = resp.json()
print("Share this URL:", data["downloadUrl"])5. Delete an upload
Delete a file before it expires. This removes it from both the database and the Walrus storage network. The share link will stop working immediately.
DELETE /v1/upload/{uploadId}/delete
X-Aion-Key: YOUR_API_KEYResponse (200)
{
"ok": true,
"status": "DELETED"
}If the file was already deleted, the endpoint returns success. If the on-chain cleanup fails, the database record is still removed and a warning field is included in the response.
Limits
- Max file size: 500 MB on Basic, 1 GB on Pro
- Rate limit: 5 uploads per key per minute
- Concurrency: 2 parallel uploads per key
- File lifetime: 30 days on Basic, while your subscription stays active on Pro
See Limits & Quotas for full details.
Next steps
- Authentication — API key best practices
- Errors & retries — Handle failures gracefully
- OpenAPI spec — Full API reference
Need an API key?
- Create API keys in seconds
- Dashboard with upload history
- No passwords
- No gas fees to sign in