curl -X POST "https://nunu.ai/api/v1/project/your-project-id/builds/upload" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "your-project-id",
"name": "Production Build v1.2.3",
"file_name": "app-release.apk",
"file_size": 52428800,
"platform": "android",
"multipart": false,
"auto_delete": true,
"tags": ["version:1.2.3", "env:production"],
"details": {
"vcs": {
"commit": { "short_hash": "a1b2c3d" },
"branch": "main"
}
}
}'
const response = await fetch("https://nunu.ai/api/v1/project/your-project-id/builds/upload", {
method: "POST",
headers: {
"X-Api-Key": process.env.NUNU_API_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify({
project_id: "your-project-id",
name: "Production Build v1.2.3",
file_name: "app-release.apk",
file_size: 52428800,
platform: "android",
multipart: false,
}),
});
const data = await response.json();
import requests
import os
response = requests.post(
"https://nunu.ai/api/v1/project/your-project-id/builds/upload",
headers={
"X-Api-Key": os.environ["NUNU_API_TOKEN"],
"Content-Type": "application/json"
},
json={
"project_id": "your-project-id",
"name": "Production Build v1.2.3",
"file_name": "app-release.apk",
"file_size": 52428800,
"platform": "android",
"multipart": False
}
)
data = response.json()
{
"upload_type": "single-part",
"build_id": "123e4567-e89b-12d3-a456-426614174000",
"upload_url": "https://storage.example.com/...",
"object_key": "abc123-def456/app-release.apk"
}
{
"upload_type": "multipart",
"build_id": "123e4567-e89b-12d3-a456-426614174000",
"upload_id": "ExampleUploadId123",
"object_key": "abc123-def456/large-app.apk",
"total_parts": 52,
"part_size": 104857600
}
{
"error": "Storage capacity exceeded",
"code": "QUOTA_EXCEEDED",
"details": {
"used": 10737418240,
"capacity": 10737418240,
"needed": 524288000
}
}
Builds
Initiate Upload
Initiate a build upload (single-part or multipart).
POST
/
project
/
{projectId}
/
builds
/
upload
curl -X POST "https://nunu.ai/api/v1/project/your-project-id/builds/upload" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "your-project-id",
"name": "Production Build v1.2.3",
"file_name": "app-release.apk",
"file_size": 52428800,
"platform": "android",
"multipart": false,
"auto_delete": true,
"tags": ["version:1.2.3", "env:production"],
"details": {
"vcs": {
"commit": { "short_hash": "a1b2c3d" },
"branch": "main"
}
}
}'
const response = await fetch("https://nunu.ai/api/v1/project/your-project-id/builds/upload", {
method: "POST",
headers: {
"X-Api-Key": process.env.NUNU_API_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify({
project_id: "your-project-id",
name: "Production Build v1.2.3",
file_name: "app-release.apk",
file_size: 52428800,
platform: "android",
multipart: false,
}),
});
const data = await response.json();
import requests
import os
response = requests.post(
"https://nunu.ai/api/v1/project/your-project-id/builds/upload",
headers={
"X-Api-Key": os.environ["NUNU_API_TOKEN"],
"Content-Type": "application/json"
},
json={
"project_id": "your-project-id",
"name": "Production Build v1.2.3",
"file_name": "app-release.apk",
"file_size": 52428800,
"platform": "android",
"multipart": False
}
)
data = response.json()
{
"upload_type": "single-part",
"build_id": "123e4567-e89b-12d3-a456-426614174000",
"upload_url": "https://storage.example.com/...",
"object_key": "abc123-def456/app-release.apk"
}
{
"upload_type": "multipart",
"build_id": "123e4567-e89b-12d3-a456-426614174000",
"upload_id": "ExampleUploadId123",
"object_key": "abc123-def456/large-app.apk",
"total_parts": 52,
"part_size": 104857600
}
{
"error": "Storage capacity exceeded",
"code": "QUOTA_EXCEEDED",
"details": {
"used": 10737418240,
"capacity": 10737418240,
"needed": 524288000
}
}
Permission Required:
project:manage-build-storageFor most users, we recommend using the CLI or GitHub Action instead of the API directly.
Upload Methods
| Method | File Size | Use Case |
|---|---|---|
| Single-part | < 3GB | Simple uploads |
| Multipart | ≥ 3GB (up to 5TB) | Large files, parallel uploads |
multipart: false.
Request Body
string
Optional. The project is taken from the URL. If provided, it must match the projectId in the route.
string
required
Build name (1-255 characters)
string
required
File name (1-255 characters)
integer
required
Size in bytes (positive integer)
string
required
Target platform:
windows, macos, linux, android, ios-native, ios-simulator, xbox, playstationstring
Description (max 1000 characters)
boolean
Force multipart upload. Auto-enabled for files ≥3GB.
boolean
default:"false"
Auto-delete old builds when storage capacity is reached
string
default:"least_recent"
Deletion policy:
least_recent (LRU) or oldest (FIFO)integer
Timeout in minutes (1-1440). Default: 10 for single-part, 60 for multipart.
string[]
Array of tags (1-50 characters each)
object
Build metadata (VCS, CI, app info)
Show details properties
Show details properties
object
object
CI/CD metadata (system, build_number, job_name, run_url)
object
App metadata (version, version_code, build_number, bundle_id)
Response (Single-Part)
string
required
Always
single-partstring
required
Build ID for completing the upload
string
required
Presigned URL to PUT the file
string
required
Object key for the upload
Response (Multipart)
string
required
Always
multipartstring
required
Build ID for completing the upload
string
required
Upload ID for getting part URLs
string
required
Object key for the upload
integer
required
Total number of parts to upload
integer
required
Size of each part in bytes (typically 100MB)
curl -X POST "https://nunu.ai/api/v1/project/your-project-id/builds/upload" \
-H "X-Api-Key: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project_id": "your-project-id",
"name": "Production Build v1.2.3",
"file_name": "app-release.apk",
"file_size": 52428800,
"platform": "android",
"multipart": false,
"auto_delete": true,
"tags": ["version:1.2.3", "env:production"],
"details": {
"vcs": {
"commit": { "short_hash": "a1b2c3d" },
"branch": "main"
}
}
}'
const response = await fetch("https://nunu.ai/api/v1/project/your-project-id/builds/upload", {
method: "POST",
headers: {
"X-Api-Key": process.env.NUNU_API_TOKEN,
"Content-Type": "application/json",
},
body: JSON.stringify({
project_id: "your-project-id",
name: "Production Build v1.2.3",
file_name: "app-release.apk",
file_size: 52428800,
platform: "android",
multipart: false,
}),
});
const data = await response.json();
import requests
import os
response = requests.post(
"https://nunu.ai/api/v1/project/your-project-id/builds/upload",
headers={
"X-Api-Key": os.environ["NUNU_API_TOKEN"],
"Content-Type": "application/json"
},
json={
"project_id": "your-project-id",
"name": "Production Build v1.2.3",
"file_name": "app-release.apk",
"file_size": 52428800,
"platform": "android",
"multipart": False
}
)
data = response.json()
{
"upload_type": "single-part",
"build_id": "123e4567-e89b-12d3-a456-426614174000",
"upload_url": "https://storage.example.com/...",
"object_key": "abc123-def456/app-release.apk"
}
{
"upload_type": "multipart",
"build_id": "123e4567-e89b-12d3-a456-426614174000",
"upload_id": "ExampleUploadId123",
"object_key": "abc123-def456/large-app.apk",
"total_parts": 52,
"part_size": 104857600
}
{
"error": "Storage capacity exceeded",
"code": "QUOTA_EXCEEDED",
"details": {
"used": 10737418240,
"capacity": 10737418240,
"needed": 524288000
}
}
Upload Flow
- Single-Part Upload
- Multipart Upload
1
Initiate
POST to
/builds/upload with multipart: false2
Upload
PUT the file to the returned
upload_url3
Complete
POST to
/builds/upload/complete1
Initiate
POST to
/builds/upload (automatic for files ≥3GB)2
Get Part URLs
GET
/builds/upload/parts for presigned URLs3
Upload Parts
PUT each part to its URL, save ETags
4
Complete
POST to
/builds/upload/complete with ETagsComplete Single-Part Example
#!/bin/bash
PROJECT_ID="your-project-id"
API_TOKEN="your-api-token"
FILE_PATH="build/app.apk"
# Step 1: Initiate
RESPONSE=$(curl -s -X POST \
"https://nunu.ai/api/v1/project/${PROJECT_ID}/builds/upload" \
-H "Content-Type: application/json" \
-H "X-Api-Key: $API_TOKEN" \
-d "{
\"project_id\": \"$PROJECT_ID\",
\"name\": \"Production Build\",
\"file_name\": \"$(basename $FILE_PATH)\",
\"file_size\": $(stat -f%z $FILE_PATH 2>/dev/null || stat -c%s $FILE_PATH),
\"platform\": \"android\",
\"multipart\": false
}")
UPLOAD_URL=$(echo $RESPONSE | jq -r '.upload_url')
BUILD_ID=$(echo $RESPONSE | jq -r '.build_id')
# Step 2: Upload
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/octet-stream" \
--data-binary "@$FILE_PATH"
# Step 3: Complete
curl -X POST \
"https://nunu.ai/api/v1/project/${PROJECT_ID}/builds/upload/complete" \
-H "Content-Type: application/json" \
-H "X-Api-Key: $API_TOKEN" \
-d "{\"build_id\": \"$BUILD_ID\"}"
echo "Upload complete! Build ID: $BUILD_ID"
async function uploadBuild(projectId: string, file: File, name: string, platform: string) {
const token = process.env.NUNU_API_TOKEN;
// Step 1: Initiate
const initRes = await fetch(`https://nunu.ai/api/v1/project/${projectId}/builds/upload`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": token },
body: JSON.stringify({
project_id: projectId,
name,
file_name: file.name,
file_size: file.size,
platform,
multipart: false,
}),
});
const { upload_url, build_id } = await initRes.json();
// Step 2: Upload
await fetch(upload_url, {
method: "PUT",
body: file,
headers: { "Content-Type": "application/octet-stream" },
});
// Step 3: Complete
await fetch(`https://nunu.ai/api/v1/project/${projectId}/builds/upload/complete`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": token },
body: JSON.stringify({ build_id }),
});
return build_id;
}
import requests
import os
def upload_build(project_id: str, file_path: str, name: str, platform: str) -> str:
token = os.environ["NUNU_API_TOKEN"]
file_size = os.path.getsize(file_path)
# Step 1: Initiate
init_res = requests.post(
f"https://nunu.ai/api/v1/project/{project_id}/builds/upload",
headers={"Content-Type": "application/json", "X-Api-Key": token},
json={
"project_id": project_id,
"name": name,
"file_name": os.path.basename(file_path),
"file_size": file_size,
"platform": platform,
"multipart": False
}
)
data = init_res.json()
# Step 2: Upload
with open(file_path, "rb") as f:
requests.put(data["upload_url"], data=f,
headers={"Content-Type": "application/octet-stream"})
# Step 3: Complete
requests.post(
f"https://nunu.ai/api/v1/project/{project_id}/builds/upload/complete",
headers={"Content-Type": "application/json", "X-Api-Key": token},
json={"build_id": data["build_id"]}
)
return data["build_id"]
Authorizations
API key for authentication
Path Parameters
The project ID. You can copy it from the project settings page or the project URL in the dashboard.
Body
application/json
Build name (1-255 chars)
File name (1-255 chars)
Size in bytes (positive integer)
Target platform
Available options:
windows, macos, linux, android, ios-native, ios-simulator, xbox, playstation Optional. The project is taken from the URL. If provided, it must match the projectId in the route.
Description (max 1000 chars)
Use multipart upload? (Auto for files ≥3GB)
Auto-delete old builds when capacity reached
Available options:
least_recent, oldest Timeout in minutes (1-1440)
Array of tags (1-50 chars each)
Optional build metadata
Show child attributes
Show child attributes