Skip to main content
NoCloud uses a two-step upload process:
  1. Get a signed URL from the Generate Signed URL endpoint
  2. Upload the file directly to the signed URL
There are two modes depending on whether you provide query parameters:
  • Non-allocated: No query parameters — returns a signed URL. Upload with a POST request using form data.
  • Pre-allocated: Pass contentType and size as query parameters to pre-allocate storage — returns a signed URL along with mediaId and mediaUrl. Upload with a PUT request.
This approach allows for efficient, direct-to-storage uploads without proxying through your backend.

Simple Upload (Non-allocated)

# Step 1: Get signed URL
SIGNED_URL_RESPONSE=$(curl -s -X GET \
  "https://api.nonefivem.com/cloud/storage/signed-url" \
  -H "Authorization: Bearer your-api-key")

# Extract the signed URL

UPLOAD_URL=$(echo $SIGNED_URL_RESPONSE | jq -r '.url')

# Step 2: Upload file via POST with form data

curl -X POST "$UPLOAD_URL" \
 -F "file=@./screenshot.png" \
 -F 'metadata={"category":"screenshots"}'

Pre-allocated Upload

Pass contentType and size to pre-allocate storage. The response includes mediaId and mediaUrl so you know the file’s location before uploading.
# Step 1: Get signed URL with pre-allocation
SIGNED_URL_RESPONSE=$(curl -s -X GET \
  "https://api.nonefivem.com/cloud/storage/signed-url?contentType=image/png&size=1048576" \
  -H "Authorization: Bearer your-api-key")

# Extract the signed URL

UPLOAD_URL=$(echo $SIGNED_URL_RESPONSE | jq -r '.url')

# Step 2: Upload file to signed URL via PUT

curl -X PUT "$UPLOAD_URL" \
 -H "Content-Type: image/png" \
 --data-binary @./screenshot.png

Upload with Metadata (Pre-allocated)

When using the pre-allocated flow, you can attach metadata via query parameters:
const params = new URLSearchParams({
  contentType: "image/png",
  size: file.size.toString(),
  // Metadata as JSON string
  metadata: JSON.stringify({
    oderId: "order_123",
    visitorId: 456,
    isPublic: true
  })
});

const response = await fetch(
  `https://api.nonefivem.com/cloud/storage/signed-url?${params}`,
  {
    headers: { Authorization: "Bearer your-api-key" }
  }
);
Metadata constraints: - Maximum 10 keys - Key names: alphanumeric, underscores, hyphens only (max 128 chars) - Values: string (max 512 chars), number, or boolean - Total metadata size: max 2KB

Upload with Progress Tracking

For larger files, you may want to track upload progress. This example uses the pre-allocated flow:
async function uploadWithProgress(file, onProgress) {
  // Get signed URL with pre-allocation
  const { url, mediaId, mediaUrl } = await getSignedUrl(file);

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.upload.addEventListener("progress", (event) => {
      if (event.lengthComputable) {
        const percentComplete = (event.loaded / event.total) * 100;
        onProgress(percentComplete);
      }
    });

    xhr.addEventListener("load", () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve({ mediaId, mediaUrl });
      } else {
        reject(new Error(`Upload failed with status ${xhr.status}`));
      }
    });

    xhr.addEventListener("error", () => reject(new Error("Upload failed")));

    xhr.open("PUT", url);
    xhr.setRequestHeader("Content-Type", file.type);
    xhr.send(file);
  });
}

// Usage
await uploadWithProgress(file, (progress) => {
  console.log(`Upload progress: ${progress.toFixed(1)}%`);
});
Signed URLs expire after 15 minutes. Generate a new URL if the upload doesn’t complete in time.