---
updatedAt: 2026-05-28T10:59:47.000Z
---

# Create S3 URL for uploading menu

Returns a presigned S3 URL for uploading a menu (new or existing). No request body required.
Use this s3 URL to upload the menu to S3. The menu will be uploaded to the S3 bucket. 

The document you upload to S3 takes the same `site_ids`, `menu` and optional
`pos_name` as the v1 menu PUT body, so see `pos_name` on that endpoint for its
full contract (name the system behind the site rather than yourself, dynamic-value,
`_WL` suffix, and omission/last-write-wins rules).

`pos_name`: optional string, up to 128 characters, e.g. `"Toast"`. This path is
asynchronous and has no response to carry `warnings`, so a rejected value is
reported instead as `details.warnings` on GET /v3/brands/{brand_id}/jobs/{job_id},
and never fails the menu.


# OpenAPI definition

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "Menu API",
    "version": "1.0"
  },
  "x-readme": {
    "explorer-enabled": false
  },
  "servers": [
    {
      "url": "https://api.developers.deliveroo.com/menu",
      "description": "Production API"
    },
    {
      "url": "https://api-sandbox.developers.deliveroo.com/menu",
      "description": "Sandbox"
    }
  ],
  "paths": {
    "/v1/brands/{brand_id}/menus/{id}": {
      "parameters": [
        {
          "name": "brand_id",
          "in": "path",
          "description": "The unique identifier for the brand (Deliveroo's integration team will provide this).",
          "schema": {
            "type": "string"
          },
          "required": true
        },
        {
          "name": "id",
          "in": "path",
          "description": "The unique identifier for the menu for the given \"brand\" context.",
          "schema": {
            "type": "string"
          },
          "required": true
        }
      ]
    },
    "/v3/brands/{brand_id}/menus/{id}": {
      "put": {
        "tags": [
          "v3 (for large menu uploads)"
        ],
        "summary": "Create S3 URL for uploading menu",
        "description": "Returns a presigned S3 URL for uploading a menu (new or existing). No request body required.\nUse this s3 URL to upload the menu to S3. The menu will be uploaded to the S3 bucket. \n\nThe document you upload to S3 takes the same `site_ids`, `menu` and optional\n`pos_name` as the v1 menu PUT body, so see `pos_name` on that endpoint for its\nfull contract (name the system behind the site rather than yourself, dynamic-value,\n`_WL` suffix, and omission/last-write-wins rules).\n\n`pos_name`: optional string, up to 128 characters, e.g. `\"Toast\"`. This path is\nasynchronous and has no response to carry `warnings`, so a rejected value is\nreported instead as `details.warnings` on GET /v3/brands/{brand_id}/jobs/{job_id},\nand never fails the menu.\n",
        "parameters": [
          {
            "$ref": "#/paths/~1v1~1brands~1{brand_id}~1menus~1{id}/parameters/0"
          },
          {
            "$ref": "#/paths/~1v1~1brands~1{brand_id}~1menus~1{id}/parameters/1"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "upload_url": {
                      "type": "string"
                    },
                    "version": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "bad request"
                  }
                },
                "schema": {
                  "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/400/content/application~1json/schema"
                }
              }
            }
          },
          "401": {
            "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/401"
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "menu not found"
                  }
                },
                "schema": {
                  "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/400/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/500"
          },
          "default": {
            "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/500"
          }
        }
      }
    },
    "/v3/brands/{brand_id}/jobs": {
      "post": {
        "tags": [
          "v3 (for large menu uploads)"
        ],
        "summary": "Publish Menu",
        "description": "Initiates menu processing and publication. The request body must specify the action and parameters as described. Currently there's only one action to publish the menu that is \"publish_menu_to_live\". After creating a job, poll the GET /jobs/{job_id} endpoint to check the status.\n",
        "parameters": [
          {
            "$ref": "#/paths/~1v1~1brands~1{brand_id}~1menus~1{id}/parameters/0"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "publish_menu_to_live"
                    ],
                    "description": "The action to perform"
                  },
                  "params": {
                    "type": "object",
                    "properties": {
                      "menu_id": {
                        "type": "string",
                        "description": "The external ID of the menu to publish"
                      },
                      "version": {
                        "type": "string",
                        "description": "Optional version to publish. If not provided, the latest version will be used."
                      }
                    },
                    "required": [
                      "menu_id"
                    ]
                  }
                },
                "required": [
                  "action",
                  "params"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "description": "The unique identifier for the created job. Use this to poll for status."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "new",
                        "in_progress",
                        "success",
                        "failed"
                      ],
                      "description": "Status of a menu processing job:\n- `new`: Job has been created and is waiting to be processed\n- `in_progress`: Job is currently being processed\n- `success`: Job completed successfully\n- `failed`: Job failed during processing\n"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "bad_request",
                    "message": "bad request"
                  }
                },
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "message": {
                          "type": "string"
                        },
                        "code": {
                          "type": "string",
                          "enum": [
                            "bad_request",
                            "not_found",
                            "500",
                            "service_unavailable",
                            "unprocessable_entity",
                            "too_many_requests",
                            "conflict",
                            "unauthorized"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "The provided API key and API token is invalid or disabled.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "unauthorized",
                    "message": "Unauthenticated"
                  }
                },
                "schema": {
                  "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/400/content/application~1json/schema"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "not_found",
                    "message": "menu not found"
                  }
                },
                "schema": {
                  "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/400/content/application~1json/schema"
                }
              }
            }
          },
          "500": {
            "description": "Something went wrong on our end. You’ll have to try again later once we’ve fixed it.",
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "500",
                    "message": "internal server error"
                  }
                },
                "schema": {
                  "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/400/content/application~1json/schema"
                }
              }
            }
          },
          "default": {
            "$ref": "#/paths/~1v3~1brands~1{brand_id}~1jobs/post/responses/500"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "OAuth2-Production": {
        "type": "oauth2",
        "description": "OAuth for Production",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://auth.developers.deliveroo.com/oauth2/token",
            "scopes": {}
          }
        }
      }
    }
  },
  "security": [
    {
      "OAuth2-Production": []
    }
  ]
}
```