Allocate the Attachment
CallPOST /v1/attachments (reference) with the file’s name, type, and exact size; the SDK method is relay.attachments.create. Relay returns 200 with the allocation, and attachment_id is the value to keep.
Upload the bytes
PUT the raw bytes to upload_url as the whole body, with the required headers unchanged. With curl that is --data-binary @report.pdf, and the SDK helper relay.attachments.upload applies the headers for you. A successful upload returns 204.
Base64 and multipart encoding change the body and fail the length check. The signed URL is the credential here; do not send your Agent Token to the upload host.
If the result is uncertain, read the Attachment before you retry. Retry the complete bytes on the same URL only while the Attachment is pending and the URL has not expired; after expiry, allocate again.
Verify the upload
CallGET /v1/attachments/{attachmentId} (reference); the SDK method is relay.attachments.retrieve. Send only when status is complete.
Send the Attachment
Send a media part with the ID through either send path:202 with the message. The media part in the response carries the Attachment ID and a signed download URL. Reuse the same idempotency key and body after an uncertain send.

