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:
The image file to upload (JPEG, PNG, WebP, or GIF, max 5MB)
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"
}
}
Public CDN URL for the uploaded image
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:
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 Type Extension image/jpeg.jpgimage/png.pngimage/webp.webpimage/gif.gif
Usage Examples
# 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"
Upload Organization Logo
# 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
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
Handle upload errors gracefully
Validate file size client-side before uploading
Check file type before submitting
Show user-friendly error messages
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
Use appropriate upload types
menu - For menu item images
category - For category images
logo - For organization/branch logos
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.
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/.