How the flow works
- Send
requestFileUploadUrlto ION. ION returnsuploadUrl,s3Key, andcontentType. - Send a PUT request with the file body and
Content-Typeheader touploadUrl. Storage returns a200response with anETagheader. - Send
createFileAttachmentto ION withs3Key,s3Etag,entityId, andfilename. ION returns thefileAttachment.
Prerequisites
- An authenticated GraphQL client. See Authentication.
- Permission to attach files to the entity you’re targeting.
- The entity ID of the object you’re attaching to. See Choose an entityId.
Step 1: Request a signed upload URL
uploadUrl is single-use and time-bound, typically for a few minutes. If you wait too long, the PUT request in step 2 fails. If that happens, request a new URL.
The contentType in the response is the value you must send in the PUT request’s Content-Type header. ION derives it from the filename extension. If the extension doesn’t reveal your file’s type, request the URL with a more specific filename.
Step 2: Upload the file
Upload the file directly using the signed URL:-i flag returns the response headers, including the ETag value:
ETag value, with the surrounding quotes stripped, is your s3Etag for step 3. ION uses it to verify that the file you uploaded matches the registration you’re about to create.
Step 3: Register the attachment
Input fields
Choose an entityId
TheentityId is not the same as the parent object’s id. ION lets multiple object types share a relationship to file attachments, so you pass the parent’s entity ID rather than its own id. To attach a file to a run step, the most common case, first fetch the run step’s entity.id:
entity.id, not runStep.id, as the entityId in step 3.
The same pattern applies to other targets:
End-to-end example in Python
The example uses the US endpoint. SetGQL_URL to your region’s API endpoint:
Common errors
Verify the attachment
After step 3, you can confirm the attachment in two ways:- In the ION UI, open the parent entity and check the attachments panel.
- Via the API, query the attachment back by ID:
downloadUrl is a fresh signed download URL, time-bound the same way the upload URL was. Don’t cache it long-term.