From f3376a5e71f18ad5320101a1eaf0850f988a260e Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 12 Oct 2026 17:29:38 +0200 Subject: [PATCH 1/8] feat(upload): Resumable uploads --- .../index.mdx} | 0 .../sdk/telemetry/attachments/upload.mdx | 119 ++++++++++++++++++ 2 files changed, 119 insertions(+) rename develop-docs/sdk/telemetry/{attachments.mdx => attachments/index.mdx} (100%) create mode 100644 develop-docs/sdk/telemetry/attachments/upload.mdx diff --git a/develop-docs/sdk/telemetry/attachments.mdx b/develop-docs/sdk/telemetry/attachments/index.mdx similarity index 100% rename from develop-docs/sdk/telemetry/attachments.mdx rename to develop-docs/sdk/telemetry/attachments/index.mdx diff --git a/develop-docs/sdk/telemetry/attachments/upload.mdx b/develop-docs/sdk/telemetry/attachments/upload.mdx new file mode 100644 index 00000000000000..1beef7a0be2711 --- /dev/null +++ b/develop-docs/sdk/telemetry/attachments/upload.mdx @@ -0,0 +1,119 @@ +--- +title: Resumable Uploads +--- + + + The upload endpoint is an experimental feature and the protocol is subject + to change. + + +For attachments larger than ~100 MiB, SDKs **SHOULD** use the resumable upload endpoint. +This allows the client to upload attachments that are much larger than what the envelope endpoint currently allows, and to resume an upload when the stream got interrupted. The endpoint is based on the [tus resumable upload protocol](https://tus.io/protocols/resumable-upload). + +## Workflow + +1. Create the upload to receive a pre-signed URL to the assigned upload location: + + ```shell + curl -X POST https://o0.ingest.sentry.io/api/0/upload/ \ + --header "Tus-Resumable: 1.0.0" \ + --header "Upload-Length: ${FILE_SIZE}" \ + --header "Upload-Metadata: sentry $(echo -n '{"attachment_type":"event.attachment"}' | base64)" + ``` + + The `Upload-Length` header **MUST** be specified. Sentry does not support the [`creation-defer-length` extension](https://tus.io/protocols/resumable-upload#post) of the tus protocol. + + The `Upload-Metadata` header follows the [TUS metadata format](https://tus.io/protocols/resumable-upload#upload-metadata). Sentry recognizes a single key, `sentry`, the value of which **MUST** be the Base64 encoding of a UTF-8 JSON object describing the attachment: + + ```json + { + "attachment_type": "event.attachment" + } + ``` + + | Field | Type | Required | Description | + | ----------------- | ------ | ------------ | ------------------------------------------------------------------ | + | `attachment_type` | String | **REQUIRED** | One of the values listed in [Attachment Types](#attachment-types). | + +1. Parse response headers necessary for proceeding: + + 1. `Upload-Granularity` (bytes) - Every `PATCH` the size in bytes of every request made to location below must be a multiple of this number (except for the final request). + 1. `Upload-Min-Chunk-Size` (bytes) - Every `PATCH` request made to location below must be at least this large (except for the final request). + 1. `Upload-Max-Chunk-Size` (bytes) - No `PATCH` request made to location below may be larger than this. If the file is larger than the maximum chunk size, split it into multiple requests (see `Upload-Offset` below). + 1. `Location` (URL) - The target of subsequent `PATCH` requests. + + Example response: + + ``` + HTTP/1.1 201 Created + Tus-Resumable: 1.0.0 + Tus-Extension: creation + Upload-Granularity: 256 + Upload-Min-Chunk-Size: 1048576 + Upload-Max-Chunk-Size: 1073741824 + Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ + ``` + + If the server replies with a [501 Not Implemented](https://www.rfc-editor.org/info/rfc9110/#status.501) response, the SDK **SHOULD** either fall back to submitting the attachment in an envelope (if it is [small enough](/sdk/foundations/envelopes/#size-limits)), or drop the attachment. + + +1. Start uploading: + + ```shell + head -c 1073741824 myfile.log | \ # Take the first `Max-Chunk-Size` bytes + curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ + --header "Tus-Resumable: 1.0.0" \ + --header "Content-Type: application/offset+octet-stream" \ + --header "Upload-Offset: 0" \ + --data-binary @- + ``` + + Example response: + +1. Resume the upload: + + Regardless of whether the previous `PATCH` request was successful, the client **SHOULD** attempt to resume the upload if it is incomplete. If the previous response from the server reports the `Upload-Offset` header, then this is the offset in bytes from which to resume. + + If the server did _not_ communicate the current server offset through the `Upload-Offset` header, or the response has been lost (for example, after a process restart), the client **MUST** make a `HEAD` request to the server to retrieve the current state: + + ```shell + curl -X HEAD https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ + ``` + + Example response: + + ``` + HTTP/1.1 204 No Content + Tus-Resumable: 1.0.0 + Upload-Offset: 53477376 + Upload-Metadata: sentry eyJhdHRhY2htZW50X3R5cGUiOiJldmVudC5hdHRhY2htZW50In0=" + ``` + + With the `Upload-Offset` known, resume the upload accordingly: + + ```shell + tail -c 53477377 myfile.log | # Skip the offset + head -c 1073741824 | \ # Take the first `Max-Chunk-Size` bytes + curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ + --header "Tus-Resumable: 1.0.0" \ + --header "Content-Type: application/offset+octet-stream" \ + --header "Upload-Offset: 0" \ + --data-binary @- + ``` + +1. Finalize the upload: + +Upon success, the server returns the **final** location of the upload in the `Location` response header: + +``` +HTTP/1.1 204 No Content +Tus-Resumable: 1.0.0 +Upload-Offset: 5695864832 +Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/?upload_length=11&upload_signature=..." +``` + +SDKs **MUST** use this location to attach a [placeholder item](/sdk/telemetry/attachments/#attachment-placeholders) to the event envelope. SDKs **MUST NOT** use the `Location` header from the create request or any intermediate requests, because it may not contain all the fields needed to create a valid placeholder. + +## Compression + +The byte sizes in the headers refer to the request body before compression, that is before applying `Content-Encoding`. This applies to `Upload-Granularity`, `Upload-Min-Chunk-Size`, `Upload-Max-Chunk-Size` and `Upload-Granularity`. The server interprets `Content-Encoding` on a `PATCH` request as a self-contained transport optimization that only concerns the current request in isolation, not the full upload. From 94dd6a840b450b8ea73477e4313eb83e4eed01a5 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 13 Oct 2026 09:16:42 +0200 Subject: [PATCH 2/8] old stuff and indent --- .../sdk/telemetry/attachments/index.mdx | 41 +------ .../sdk/telemetry/attachments/upload.mdx | 109 +++++++++--------- 2 files changed, 54 insertions(+), 96 deletions(-) diff --git a/develop-docs/sdk/telemetry/attachments/index.mdx b/develop-docs/sdk/telemetry/attachments/index.mdx index 417171cdd8f4a6..e2b96ca00ac23c 100644 --- a/develop-docs/sdk/telemetry/attachments/index.mdx +++ b/develop-docs/sdk/telemetry/attachments/index.mdx @@ -349,46 +349,7 @@ Attachment placeholders use item type `"attachment"` with content type `"applica SDKs **MUST** send placeholders in the same envelope as the event the file is attached to. -**Workflow:** - -1. If an attachment is larger than ~100 MiB, upload it to the [TUS](https://tus.io/protocols/resumable-upload#creation-with-upload)-conformant upload endpoint: - 1. Create the upload: - - ```shell - curl -X POST https://o0.ingest.sentry.io/api/0/upload/ \ - --header "Tus-Resumable: 1.0.0" \ - --header "Upload-Length: ${FILE_SIZE}" \ - --header "Upload-Metadata: sentry $(echo -n '{"attachment_type":"event.attachment"}' | base64)" - ``` - - The `Upload-Metadata` header follows the [TUS metadata format](https://tus.io/protocols/resumable-upload#upload-metadata). Sentry recognizes a single key, `sentry`, the value of which **MUST** be the Base64 encoding of a UTF-8 JSON object describing the attachment: - - ```json - { - "attachment_type": "event.attachment" - } - ``` - - | Field | Type | Required | Description | - | ----------------- | ------ | ------------ | ------------------------------------------------------------------ | - | `attachment_type` | String | **REQUIRED** | One of the values listed in [Attachment Types](#attachment-types). | - - 1. Get the location response header: - ``` - HTTP/1.1 201 Created - Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ - Tus-Resumable: 1.0.0 - ``` - 1. Upload the file: - ```shell - curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ - --header "Tus-Resumable: 1.0.0" \ - --header "Content-Type: application/offset+octet-stream" \ - --header "Upload-Offset: 0" \ - --data @myfile.log - ``` - -1. Upon successful upload, attach a placeholder item to the event envelope, using the `Location` header from the upload response. +See [Resumable Uploads](/sdk/telemetry/attachments/upload.mdx/) for a detailed guide on how to obtain an attachment placeholder. diff --git a/develop-docs/sdk/telemetry/attachments/upload.mdx b/develop-docs/sdk/telemetry/attachments/upload.mdx index 1beef7a0be2711..74b25446a6a93f 100644 --- a/develop-docs/sdk/telemetry/attachments/upload.mdx +++ b/develop-docs/sdk/telemetry/attachments/upload.mdx @@ -37,82 +37,79 @@ This allows the client to upload attachments that are much larger than what the 1. Parse response headers necessary for proceeding: - 1. `Upload-Granularity` (bytes) - Every `PATCH` the size in bytes of every request made to location below must be a multiple of this number (except for the final request). - 1. `Upload-Min-Chunk-Size` (bytes) - Every `PATCH` request made to location below must be at least this large (except for the final request). - 1. `Upload-Max-Chunk-Size` (bytes) - No `PATCH` request made to location below may be larger than this. If the file is larger than the maximum chunk size, split it into multiple requests (see `Upload-Offset` below). - 1. `Location` (URL) - The target of subsequent `PATCH` requests. + 1. `Upload-Granularity` (bytes) - Every `PATCH` the size in bytes of every request made to location below must be a multiple of this number (except for the final request). + 1. `Upload-Min-Chunk-Size` (bytes) - Every `PATCH` request made to location below must be at least this large (except for the final request). + 1. `Upload-Max-Chunk-Size` (bytes) - No `PATCH` request made to location below may be larger than this. If the file is larger than the maximum chunk size, split it into multiple requests (see `Upload-Offset` below). + 1. `Location` (URL) - The target of subsequent `PATCH` requests. - Example response: - - ``` - HTTP/1.1 201 Created - Tus-Resumable: 1.0.0 - Tus-Extension: creation - Upload-Granularity: 256 - Upload-Min-Chunk-Size: 1048576 - Upload-Max-Chunk-Size: 1073741824 - Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ - ``` + Example response: - If the server replies with a [501 Not Implemented](https://www.rfc-editor.org/info/rfc9110/#status.501) response, the SDK **SHOULD** either fall back to submitting the attachment in an envelope (if it is [small enough](/sdk/foundations/envelopes/#size-limits)), or drop the attachment. + ``` + HTTP/1.1 201 Created + Tus-Resumable: 1.0.0 + Tus-Extension: creation + Upload-Granularity: 256 + Upload-Min-Chunk-Size: 1048576 + Upload-Max-Chunk-Size: 1073741824 + Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ + ``` + If the server replies with a [501 Not Implemented](https://www.rfc-editor.org/info/rfc9110/#status.501) response, the SDK **SHOULD** either fall back to submitting the attachment in an envelope (if it is [small enough](/sdk/foundations/envelopes/#size-limits)), or drop the attachment. 1. Start uploading: - ```shell - head -c 1073741824 myfile.log | \ # Take the first `Max-Chunk-Size` bytes - curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ - --header "Tus-Resumable: 1.0.0" \ - --header "Content-Type: application/offset+octet-stream" \ - --header "Upload-Offset: 0" \ - --data-binary @- - ``` - - Example response: + ```shell + head -c 1073741824 myfile.log | \ # Take the first `Max-Chunk-Size` bytes + curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ + --header "Tus-Resumable: 1.0.0" \ + --header "Content-Type: application/offset+octet-stream" \ + --header "Upload-Offset: 0" \ + --data-binary @- + ``` 1. Resume the upload: - Regardless of whether the previous `PATCH` request was successful, the client **SHOULD** attempt to resume the upload if it is incomplete. If the previous response from the server reports the `Upload-Offset` header, then this is the offset in bytes from which to resume. + Regardless of whether the previous `PATCH` request was successful, the client **SHOULD** attempt to resume the upload if it is incomplete. If the previous response from the server reports the `Upload-Offset` header, then this is the offset in bytes from which to resume. - If the server did _not_ communicate the current server offset through the `Upload-Offset` header, or the response has been lost (for example, after a process restart), the client **MUST** make a `HEAD` request to the server to retrieve the current state: + If the server did _not_ communicate the current server offset through the `Upload-Offset` header, or the response has been lost (for example, after a process restart), the client **MUST** make a `HEAD` request to the server to retrieve the current state: - ```shell - curl -X HEAD https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ - ``` + ```shell + curl -X HEAD https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ + ``` Example response: ``` - HTTP/1.1 204 No Content - Tus-Resumable: 1.0.0 - Upload-Offset: 53477376 - Upload-Metadata: sentry eyJhdHRhY2htZW50X3R5cGUiOiJldmVudC5hdHRhY2htZW50In0=" - ``` - - With the `Upload-Offset` known, resume the upload accordingly: - - ```shell - tail -c 53477377 myfile.log | # Skip the offset - head -c 1073741824 | \ # Take the first `Max-Chunk-Size` bytes - curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ - --header "Tus-Resumable: 1.0.0" \ - --header "Content-Type: application/offset+octet-stream" \ - --header "Upload-Offset: 0" \ - --data-binary @- - ``` + HTTP/1.1 204 No Content + Tus-Resumable: 1.0.0 + Upload-Offset: 53477376 + Upload-Metadata: sentry eyJhdHRhY2htZW50X3R5cGUiOiJldmVudC5hdHRhY2htZW50In0=" + ``` + + With the `Upload-Offset` known, resume the upload accordingly: + + ```shell + tail -c 53477377 myfile.log | # Skip the offset + head -c 1073741824 | \ # Take the first `Max-Chunk-Size` bytes + curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ + --header "Tus-Resumable: 1.0.0" \ + --header "Content-Type: application/offset+octet-stream" \ + --header "Upload-Offset: 0" \ + --data-binary @- + ``` 1. Finalize the upload: -Upon success, the server returns the **final** location of the upload in the `Location` response header: + Upon success, the server returns the **final** location of the upload in the `Location` response header: -``` -HTTP/1.1 204 No Content -Tus-Resumable: 1.0.0 -Upload-Offset: 5695864832 -Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/?upload_length=11&upload_signature=..." -``` + ``` + HTTP/1.1 204 No Content + Tus-Resumable: 1.0.0 + Upload-Offset: 5695864832 + Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/?upload_length=11&upload_signature=..." + ``` -SDKs **MUST** use this location to attach a [placeholder item](/sdk/telemetry/attachments/#attachment-placeholders) to the event envelope. SDKs **MUST NOT** use the `Location` header from the create request or any intermediate requests, because it may not contain all the fields needed to create a valid placeholder. + SDKs **MUST** use this location to attach a [placeholder item](/sdk/telemetry/attachments/#attachment-placeholders) to the event envelope. SDKs **MUST NOT** use the `Location` header from the create request or any intermediate requests, because it may not contain all the fields needed to create a valid placeholder. ## Compression From 43abebef1b24d1fa04f432e920b26d8bf1344bd5 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 13 Oct 2026 09:50:11 +0200 Subject: [PATCH 3/8] little fixies --- .../sdk/telemetry/attachments/upload.mdx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/develop-docs/sdk/telemetry/attachments/upload.mdx b/develop-docs/sdk/telemetry/attachments/upload.mdx index 74b25446a6a93f..019865b019f27b 100644 --- a/develop-docs/sdk/telemetry/attachments/upload.mdx +++ b/develop-docs/sdk/telemetry/attachments/upload.mdx @@ -33,11 +33,11 @@ This allows the client to upload attachments that are much larger than what the | Field | Type | Required | Description | | ----------------- | ------ | ------------ | ------------------------------------------------------------------ | - | `attachment_type` | String | **REQUIRED** | One of the values listed in [Attachment Types](#attachment-types). | + | `attachment_type` | String | **REQUIRED** | One of the values listed in [Attachment Types](/sdk/telemetry/attachments/#attachment-types). | 1. Parse response headers necessary for proceeding: - 1. `Upload-Granularity` (bytes) - Every `PATCH` the size in bytes of every request made to location below must be a multiple of this number (except for the final request). + 1. `Upload-Granularity` (bytes) - Every `PATCH` request made to location below must be a multiple of this number (except for the final request). 1. `Upload-Min-Chunk-Size` (bytes) - Every `PATCH` request made to location below must be at least this large (except for the final request). 1. `Upload-Max-Chunk-Size` (bytes) - No `PATCH` request made to location below may be larger than this. If the file is larger than the maximum chunk size, split it into multiple requests (see `Upload-Offset` below). 1. `Location` (URL) - The target of subsequent `PATCH` requests. @@ -71,13 +71,13 @@ This allows the client to upload attachments that are much larger than what the Regardless of whether the previous `PATCH` request was successful, the client **SHOULD** attempt to resume the upload if it is incomplete. If the previous response from the server reports the `Upload-Offset` header, then this is the offset in bytes from which to resume. - If the server did _not_ communicate the current server offset through the `Upload-Offset` header, or the response has been lost (for example, after a process restart), the client **MUST** make a `HEAD` request to the server to retrieve the current state: + If the server did _not_ communicate the current server offset through the `Upload-Offset` header, or the response has been lost (for example, after a process restart), the client **MUST** make a `HEAD` request to the upload location to retrieve the current state: ```shell curl -X HEAD https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ ``` - Example response: + Example response: ``` HTTP/1.1 204 No Content @@ -86,7 +86,7 @@ This allows the client to upload attachments that are much larger than what the Upload-Metadata: sentry eyJhdHRhY2htZW50X3R5cGUiOiJldmVudC5hdHRhY2htZW50In0=" ``` - With the `Upload-Offset` known, resume the upload accordingly: + Plug the returned `Upload-Offset` into the next patch request to resume: ```shell tail -c 53477377 myfile.log | # Skip the offset @@ -94,7 +94,7 @@ This allows the client to upload attachments that are much larger than what the curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ --header "Tus-Resumable: 1.0.0" \ --header "Content-Type: application/offset+octet-stream" \ - --header "Upload-Offset: 0" \ + --header "Upload-Offset: 53477376" \ --data-binary @- ``` @@ -109,8 +109,8 @@ This allows the client to upload attachments that are much larger than what the Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/?upload_length=11&upload_signature=..." ``` - SDKs **MUST** use this location to attach a [placeholder item](/sdk/telemetry/attachments/#attachment-placeholders) to the event envelope. SDKs **MUST NOT** use the `Location` header from the create request or any intermediate requests, because it may not contain all the fields needed to create a valid placeholder. + SDKs **MUST** use this location to attach a [placeholder item](/sdk/telemetry/attachments/#attachment-placeholders) to the event envelope. SDKs **MUST NOT** use the `Location` header from the initial `POST` request, or any intermediate requests, because it may not contain all the fields needed to create a valid placeholder. ## Compression -The byte sizes in the headers refer to the request body before compression, that is before applying `Content-Encoding`. This applies to `Upload-Granularity`, `Upload-Min-Chunk-Size`, `Upload-Max-Chunk-Size` and `Upload-Granularity`. The server interprets `Content-Encoding` on a `PATCH` request as a self-contained transport optimization that only concerns the current request in isolation, not the full upload. +The byte sizes in the headers refer to the request body _before_ compression, that is, before applying `Content-Encoding`. This applies to `Upload-Granularity`, `Upload-Min-Chunk-Size`, `Upload-Max-Chunk-Size` and `Upload-Offset`. The server interprets `Content-Encoding` on a `PATCH` request as a self-contained transport optimization that only concerns the current request in isolation, not the full upload. From 30d46e1ca3805cd9ef0c7fe56bb733837520d13a Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 13 Oct 2026 10:26:31 +0200 Subject: [PATCH 4/8] x-decoded-content-length --- develop-docs/sdk/telemetry/attachments/upload.mdx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/develop-docs/sdk/telemetry/attachments/upload.mdx b/develop-docs/sdk/telemetry/attachments/upload.mdx index 019865b019f27b..402533e44196df 100644 --- a/develop-docs/sdk/telemetry/attachments/upload.mdx +++ b/develop-docs/sdk/telemetry/attachments/upload.mdx @@ -56,7 +56,7 @@ This allows the client to upload attachments that are much larger than what the If the server replies with a [501 Not Implemented](https://www.rfc-editor.org/info/rfc9110/#status.501) response, the SDK **SHOULD** either fall back to submitting the attachment in an envelope (if it is [small enough](/sdk/foundations/envelopes/#size-limits)), or drop the attachment. -1. Start uploading: +1. Upload the first chunk: ```shell head -c 1073741824 myfile.log | \ # Take the first `Max-Chunk-Size` bytes @@ -64,9 +64,12 @@ This allows the client to upload attachments that are much larger than what the --header "Tus-Resumable: 1.0.0" \ --header "Content-Type: application/offset+octet-stream" \ --header "Upload-Offset: 0" \ + --header "X-Decoded-Content-Length: 1073741824" --data-binary @- ``` + The client **SHOULD** compress the request body to speed up the upload (see [Request Compression](/sdk/foundations/transport/compression/)). When it does, the `Content-Length` header refers to the compressed length of the current chunk. Therefore, the client **MUST** send the custom `X-Decoded-Content-Length` header containing the original, uncompressed length of the current chunk, to inform the storage backend about its size upfront. + 1. Resume the upload: Regardless of whether the previous `PATCH` request was successful, the client **SHOULD** attempt to resume the upload if it is incomplete. If the previous response from the server reports the `Upload-Offset` header, then this is the offset in bytes from which to resume. @@ -95,12 +98,13 @@ This allows the client to upload attachments that are much larger than what the --header "Tus-Resumable: 1.0.0" \ --header "Content-Type: application/offset+octet-stream" \ --header "Upload-Offset: 53477376" \ + --header "X-Decoded-Content-Length: 2345256" --data-binary @- ``` 1. Finalize the upload: - Upon success, the server returns the **final** location of the upload in the `Location` response header: + Once the total number of uploaded bytes matches the `Upload-Length` that was announced in the initial `POST` request, the server returns the **final** location of the upload in the `Location` response header: ``` HTTP/1.1 204 No Content @@ -109,7 +113,7 @@ This allows the client to upload attachments that are much larger than what the Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/?upload_length=11&upload_signature=..." ``` - SDKs **MUST** use this location to attach a [placeholder item](/sdk/telemetry/attachments/#attachment-placeholders) to the event envelope. SDKs **MUST NOT** use the `Location` header from the initial `POST` request, or any intermediate requests, because it may not contain all the fields needed to create a valid placeholder. + SDKs **MUST** use this location to attach a [placeholder item](/sdk/telemetry/attachments/#attachment-placeholders) to the event envelope. SDKs **MUST NOT** use the `Location` header from the initial `POST` request, or any intermediate request, because it may not contain all the fields needed to create a valid placeholder. ## Compression From a3a7cb7450bbb9e85f8b29f946e50ea8d89bf708 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 13 Oct 2026 10:28:05 +0200 Subject: [PATCH 5/8] fix link --- develop-docs/sdk/telemetry/attachments/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/develop-docs/sdk/telemetry/attachments/index.mdx b/develop-docs/sdk/telemetry/attachments/index.mdx index e2b96ca00ac23c..b42923cffe5ea8 100644 --- a/develop-docs/sdk/telemetry/attachments/index.mdx +++ b/develop-docs/sdk/telemetry/attachments/index.mdx @@ -349,7 +349,7 @@ Attachment placeholders use item type `"attachment"` with content type `"applica SDKs **MUST** send placeholders in the same envelope as the event the file is attached to. -See [Resumable Uploads](/sdk/telemetry/attachments/upload.mdx/) for a detailed guide on how to obtain an attachment placeholder. +See [Resumable Uploads](/sdk/telemetry/attachments/upload/) for a detailed guide on how to obtain an attachment placeholder. From 1138b1dc4d76ebf41718f214b9f384fe257ad637 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Tue, 13 Oct 2026 15:20:34 +0200 Subject: [PATCH 6/8] more fixes --- develop-docs/sdk/telemetry/attachments/upload.mdx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/develop-docs/sdk/telemetry/attachments/upload.mdx b/develop-docs/sdk/telemetry/attachments/upload.mdx index 402533e44196df..597c6212acd54e 100644 --- a/develop-docs/sdk/telemetry/attachments/upload.mdx +++ b/develop-docs/sdk/telemetry/attachments/upload.mdx @@ -85,20 +85,19 @@ This allows the client to upload attachments that are much larger than what the ``` HTTP/1.1 204 No Content Tus-Resumable: 1.0.0 - Upload-Offset: 53477376 + Upload-Offset: 1073741824 Upload-Metadata: sentry eyJhdHRhY2htZW50X3R5cGUiOiJldmVudC5hdHRhY2htZW50In0=" ``` Plug the returned `Upload-Offset` into the next patch request to resume: ```shell - tail -c 53477377 myfile.log | # Skip the offset - head -c 1073741824 | \ # Take the first `Max-Chunk-Size` bytes + tail -c 1073741825 myfile.log | \ # Skip the offset curl -X PATCH https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ \ --header "Tus-Resumable: 1.0.0" \ --header "Content-Type: application/offset+octet-stream" \ - --header "Upload-Offset: 53477376" \ - --header "X-Decoded-Content-Length: 2345256" + --header "Upload-Offset: 1073741824" \ + --header "X-Decoded-Content-Length: 2345256" \ # remaining file size --data-binary @- ``` @@ -109,8 +108,8 @@ This allows the client to upload attachments that are much larger than what the ``` HTTP/1.1 204 No Content Tus-Resumable: 1.0.0 - Upload-Offset: 5695864832 - Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/?upload_length=11&upload_signature=..." + Upload-Offset: 1076087080 + Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/?upload_length=1076087080&upload_signature=..." ``` SDKs **MUST** use this location to attach a [placeholder item](/sdk/telemetry/attachments/#attachment-placeholders) to the event envelope. SDKs **MUST NOT** use the `Location` header from the initial `POST` request, or any intermediate request, because it may not contain all the fields needed to create a valid placeholder. From 7ce47ae2b504026ef485b2560d2f06e96b11eb66 Mon Sep 17 00:00:00 2001 From: Tobias Wilfert <36408720+tobias-wilfert@users.noreply.github.com> Date: Fri, 16 Oct 2026 12:15:13 +0200 Subject: [PATCH 7/8] fix: `Upload-Chunk-Granularity` instead of `Upload-Granularity` --- develop-docs/sdk/telemetry/attachments/upload.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/develop-docs/sdk/telemetry/attachments/upload.mdx b/develop-docs/sdk/telemetry/attachments/upload.mdx index 597c6212acd54e..6e7c8ba1cdefd0 100644 --- a/develop-docs/sdk/telemetry/attachments/upload.mdx +++ b/develop-docs/sdk/telemetry/attachments/upload.mdx @@ -37,7 +37,7 @@ This allows the client to upload attachments that are much larger than what the 1. Parse response headers necessary for proceeding: - 1. `Upload-Granularity` (bytes) - Every `PATCH` request made to location below must be a multiple of this number (except for the final request). + 1. `Upload-Chunk-Granularity` (bytes) - Every `PATCH` request made to location below must be a multiple of this number (except for the final request). 1. `Upload-Min-Chunk-Size` (bytes) - Every `PATCH` request made to location below must be at least this large (except for the final request). 1. `Upload-Max-Chunk-Size` (bytes) - No `PATCH` request made to location below may be larger than this. If the file is larger than the maximum chunk size, split it into multiple requests (see `Upload-Offset` below). 1. `Location` (URL) - The target of subsequent `PATCH` requests. @@ -48,7 +48,7 @@ This allows the client to upload attachments that are much larger than what the HTTP/1.1 201 Created Tus-Resumable: 1.0.0 Tus-Extension: creation - Upload-Granularity: 256 + Upload-Chunk-Granularity: 256 Upload-Min-Chunk-Size: 1048576 Upload-Max-Chunk-Size: 1073741824 Location: https://o0.ingest.sentry.io/api/0/upload/24e533e02ec3bc40c387f1a0e460e216/ @@ -116,4 +116,4 @@ This allows the client to upload attachments that are much larger than what the ## Compression -The byte sizes in the headers refer to the request body _before_ compression, that is, before applying `Content-Encoding`. This applies to `Upload-Granularity`, `Upload-Min-Chunk-Size`, `Upload-Max-Chunk-Size` and `Upload-Offset`. The server interprets `Content-Encoding` on a `PATCH` request as a self-contained transport optimization that only concerns the current request in isolation, not the full upload. +The byte sizes in the headers refer to the request body _before_ compression, that is, before applying `Content-Encoding`. This applies to `Upload-Chunk-Granularity`, `Upload-Min-Chunk-Size`, `Upload-Max-Chunk-Size` and `Upload-Offset`. The server interprets `Content-Encoding` on a `PATCH` request as a self-contained transport optimization that only concerns the current request in isolation, not the full upload. From 275bd3982af97bdf2397ca5aeee3238c3d7a8303 Mon Sep 17 00:00:00 2001 From: Joris Bayer Date: Mon, 19 Oct 2026 09:31:12 +0200 Subject: [PATCH 8/8] no-store --- develop-docs/sdk/telemetry/attachments/upload.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/develop-docs/sdk/telemetry/attachments/upload.mdx b/develop-docs/sdk/telemetry/attachments/upload.mdx index 6e7c8ba1cdefd0..6f3160b106ce99 100644 --- a/develop-docs/sdk/telemetry/attachments/upload.mdx +++ b/develop-docs/sdk/telemetry/attachments/upload.mdx @@ -86,7 +86,7 @@ This allows the client to upload attachments that are much larger than what the HTTP/1.1 204 No Content Tus-Resumable: 1.0.0 Upload-Offset: 1073741824 - Upload-Metadata: sentry eyJhdHRhY2htZW50X3R5cGUiOiJldmVudC5hdHRhY2htZW50In0=" + Cache-Control: no-store ``` Plug the returned `Upload-Offset` into the next patch request to resume: