> ## Documentation Index > Fetch the complete documentation index at: https://docs.restaurant365.com/llms.txt > Use this file to discover all available pages before exploring further. # Upload File to S3 > Step 2 of 3. **This endpoint is not hosted by Restaurant365.** Upload the file from your device, as a binary request body, to the `url` returned by Generate Upload URL. Use the complete `url` exactly as returned, including its query string — the signature is part of it. The URL carries its own authorization, so do not send your Authorization or x-r365-context-tenant-id header. There are no parameters or headers to fill in by hand in most tools; they set the Content-Length and Content-Type headers for you from the file. The file must be the one you described in Generate Upload URL: **the same size in bytes and the same file type**. A different size or type is rejected with HTTP 403 SignatureDoesNotMatch, with no tolerance — a file one byte larger than declared is rejected. The file's own name is not checked. A successful upload returns 200 with an empty body. Continue to Register Attachment. ## OpenAPI ````json PUT /{url_from_step_1} { "openapi": "3.0.1", "info": { "title": "R365 Public API", "contact": { "name": "R365 Services Team", "email": "r365@restaurant365.com" }, "version": "1.0" }, "security": [ { "Bearer": [] } ], "paths": { "/{url_from_step_1}": { "put": { "tags": [ "accounting" ], "summary": "Upload File to S3", "description": "Step 2 of 3. **This endpoint is not hosted by Restaurant365.** Upload the file from your device, as a binary request body, to the `url` returned by Generate Upload URL.\n\nUse the complete `url` exactly as returned, including its query string — the signature is part of it. The URL carries its own authorization, so do not send your Authorization or x-r365-context-tenant-id header. There are no parameters or headers to fill in by hand in most tools; they set the Content-Length and Content-Type headers for you from the file.\n\nThe file must be the one you described in Generate Upload URL: **the same size in bytes and the same file type**. A different size or type is rejected with HTTP 403 SignatureDoesNotMatch, with no tolerance — a file one byte larger than declared is rejected. The file's own name is not checked.\n\nA successful upload returns 200 with an empty body. Continue to Register Attachment.", "parameters": [ { "name": "url_from_step_1", "in": "path", "required": true, "description": "The complete url returned by Generate Upload URL, used as the whole request URL including its query string. Do not build it yourself.", "schema": { "type": "string" } } ], "requestBody": { "required": true, "description": "The file itself, uploaded from your device as binary data. It must be the same file you described in Generate Upload URL: the same size in bytes and the same file type.", "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } } }, "responses": { "200": { "description": "The file was uploaded. Continue to Register Attachment." }, "403": { "description": "SignatureDoesNotMatch — the file's size or type does not match what was declared in Generate Upload URL, or the URL has expired. Generate a new URL and retry; signed URLs cannot be refreshed." } }, "x-d360-narrative-url": "
Attaching a file to an existing AP transaction takes three calls. Only the first and third are Restaurant365 endpoints; the second is a direct upload to Amazon S3 using a URL the first call returns.
Generate Upload URL — POST /public/v1/accounting/accounts-payable/attachments/upload-url
Upload File to S3 — upload the file from your device to the returned URL
Register Attachment — POST /public/v1/accounting/accounts-payable/attachments
Supported AP transaction types are invoices, credit memos and payments. The transaction must already exist; attaching a file is a separate operation from creating the transaction.