Skip to main content
The Uploads API allows you to upload images to RestAI’s cloud storage (Cloudflare R2) and retrieve public URLs for use in menu items, categories, and organization branding.

Supported File Types

The API accepts the following image formats:
  • JPEG (image/jpeg)
  • PNG (image/png)
  • WebP (image/webp)
  • GIF (image/gif)

File Size Limits

  • Maximum size: 5 MB per file
  • Files exceeding this limit will be rejected with a BAD_REQUEST error

Upload Types

Images are organized by upload type:
  • menu - Menu item images
  • logo - Organization or branch logos
  • category - Category images

Upload Image

POST /api/uploads

Upload a single image file
Authentication Required: Yes (Bearer token) Content Type: multipart/form-data Form Data Parameters:
file
file
required
The image file to upload (JPEG, PNG, WebP, or GIF, max 5MB)
type
string
default:"menu"
Upload type: menu, logo, or category
Request:
curl -X POST https://api.restai.com/v1/api/uploads \
  -H "Authorization: Bearer <token>" \
  -F "file=@/path/to/image.jpg" \
  -F "type=menu"
Response:
{
  "success": true,
  "data": {
    "url": "https://cdn.restai.com/123e4567-e89b-12d3-a456-426614174000/menu/a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d.jpg",
    "key": "123e4567-e89b-12d3-a456-426614174000/menu/a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d.jpg"
  }
}
url
string
Public CDN URL for the uploaded image
key
string
Storage key (used for deletion)
Error Responses:
// 400 Bad Request - No file provided
{
  "success": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "Se requiere un archivo"
  }
}

// 400 Bad Request - Invalid file type
{
  "success": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "Tipo de archivo no permitido. Usa JPEG, PNG, WebP o GIF"
  }
}

// 400 Bad Request - File too large
{
  "success": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "El archivo excede el tamaño máximo de 5MB"
  }
}

// 400 Bad Request - Invalid upload type
{
  "success": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "Tipo de upload inválido. Usa menu, logo o category"
  }
}

Delete Image

DELETE /api/uploads/:key

Delete a previously uploaded image
Authentication Required: Yes (Bearer token) Path Parameters:
key
string
required
The storage key returned when the file was uploaded (URL-encoded if necessary)
Request:
curl -X DELETE "https://api.restai.com/v1/api/uploads/123e4567-e89b-12d3-a456-426614174000/menu/a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d.jpg" \
  -H "Authorization: Bearer <token>"
Response:
{
  "success": true,
  "data": {
    "deleted": "123e4567-e89b-12d3-a456-426614174000/menu/a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d.jpg"
  }
}
Error Responses:
// 400 Bad Request - No key provided
{
  "success": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "Se requiere la key del archivo"
  }
}

Storage Structure

Files are stored using the following path structure:
{organization_id}/{upload_type}/{uuid}.{extension}
Examples:
  • Menu item: 123e4567-e89b-12d3-a456-426614174000/menu/a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d.jpg
  • Logo: 123e4567-e89b-12d3-a456-426614174000/logo/b2c3d4e5-f6a7-5b6c-9d0e-1f2a3b4c5d6e.png
  • Category: 123e4567-e89b-12d3-a456-426614174000/category/c3d4e5f6-a7b8-6c7d-0e1f-2a3b4c5d6e7f.webp

File Extension Mapping

The API automatically determines the file extension based on MIME type:
MIME TypeExtension
image/jpeg.jpg
image/png.png
image/webp.webp
image/gif.gif

Usage Examples

Upload Menu Item Image

# 1. Upload the image
curl -X POST https://api.restai.com/v1/api/uploads \
  -H "Authorization: Bearer <token>" \
  -F "[email protected]" \
  -F "type=menu"

# Response:
# {
#   "success": true,
#   "data": {
#     "url": "https://cdn.restai.com/.../menu/abc123.jpg",
#     "key": ".../menu/abc123.jpg"
#   }
# }

# 2. Use the URL when creating a menu item
curl -X POST https://api.restai.com/v1/menu/items \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "categoryId": "...",
    "name": "Ceviche Clásico",
    "price": 3500,
    "imageUrl": "https://cdn.restai.com/.../menu/abc123.jpg"
  }'

Upload Category Image

curl -X POST https://api.restai.com/v1/api/uploads \
  -H "Authorization: Bearer <token>" \
  -F "[email protected]" \
  -F "type=category"
# 1. Upload logo
curl -X POST https://api.restai.com/v1/api/uploads \
  -H "Authorization: Bearer <token>" \
  -F "[email protected]" \
  -F "type=logo"

# 2. Update organization settings with logo URL
curl -X PATCH https://api.restai.com/v1/api/settings/org \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "logoUrl": "https://cdn.restai.com/.../logo/xyz789.png"
  }'

Delete an Image

# Delete using the key from upload response
curl -X DELETE "https://api.restai.com/v1/api/uploads/123e4567.../menu/abc123.jpg" \
  -H "Authorization: Bearer <token>"

JavaScript/TypeScript Example

// Upload image from file input
async function uploadImage(file: File, type: 'menu' | 'logo' | 'category') {
  const formData = new FormData();
  formData.append('file', file);
  formData.append('type', type);

  const response = await fetch('https://api.restai.com/v1/api/uploads', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
    },
    body: formData,
  });

  const result = await response.json();
  
  if (result.success) {
    console.log('Image URL:', result.data.url);
    console.log('Storage key:', result.data.key);
    return result.data;
  } else {
    throw new Error(result.error.message);
  }
}

// Delete image by key
async function deleteImage(key: string) {
  const response = await fetch(`https://api.restai.com/v1/api/uploads/${key}`, {
    method: 'DELETE',
    headers: {
      'Authorization': `Bearer ${token}`,
    },
  });

  const result = await response.json();
  return result.success;
}

Best Practices

  1. Optimize images before upload
    • Compress images to reduce file size
    • Use WebP format for better compression and quality
    • Recommended max dimensions: 1200x1200px for menu items, 800x600px for categories
  2. Handle upload errors gracefully
    • Validate file size client-side before uploading
    • Check file type before submitting
    • Show user-friendly error messages
  3. Delete unused images
    • When updating a menu item’s image, delete the old image using the DELETE endpoint
    • Keep track of image keys to enable cleanup
  4. Use appropriate upload types
    • menu - For menu item images
    • category - For category images
    • logo - For organization/branch logos
  5. CDN URLs are permanent
    • Once uploaded, the URL remains valid until explicitly deleted
    • Safe to store URLs in your database
    • Images are served via CDN for fast global delivery
Consider implementing client-side image compression using libraries like browser-image-compression to reduce upload times and bandwidth usage.
The DELETE endpoint does not verify ownership. Make sure to only delete images that belong to your organization. The API uses tenant middleware to scope uploads to your organization.

Image Optimization Tools

  • TinyPNG/TinyJPG: Online compression tool
  • ImageOptim: Mac app for image optimization
  • Squoosh: Web-based image compression from Google
  • Sharp: Node.js library for server-side image processing

Cloudflare R2 Integration

RestAI uses Cloudflare R2 for object storage, providing:
  • Global CDN: Fast image delivery worldwide
  • No egress fees: Unlimited bandwidth at no extra cost
  • High availability: 99.9% uptime SLA
  • Automatic scaling: No storage limits
All uploaded images are automatically available via CDN at https://cdn.restai.com/.

Build docs developers (and LLMs) love