Skip to main content

Attachments API

Create, resume, download, and delete attachments across Qarion resources.

Product And Ticket Endpoints

MethodEndpointDescription
GET/catalog/spaces/{slug}/products/{product_id}/attachmentsList product attachments.
POST/catalog/spaces/{slug}/products/{product_id}/attachmentsUpload a product attachment with legacy multipart form data.
POST/catalog/spaces/{slug}/products/{product_id}/attachments/transfersInitiate a resumable product attachment upload.
POST/catalog/spaces/{slug}/products/{product_id}/attachments/transfers/{transfer_id}/completeFinalize a stored transfer as a product attachment.
GET, HEAD/catalog/spaces/{slug}/products/{product_id}/attachments/{attachment_id}/downloadDownload product attachment content or metadata.
DELETE/catalog/spaces/{slug}/products/{product_id}/attachments/{attachment_id}Delete a product attachment.
POST/issues/tickets/{ticket_id}/attachments/transfersInitiate a resumable ticket attachment upload.
POST/issues/tickets/{ticket_id}/attachments/transfers/{transfer_id}/completeFinalize a ticket attachment.
GET, HEAD/issues/attachments/{attachment_id}/downloadDownload ticket attachment content or metadata.
DELETE/issues/attachments/{attachment_id}Delete a ticket attachment.

Equivalent initiate and complete routes are available on supported data domains, master-data datasets, meetings, document pages, analyses and analysis assets, repositories, contracts, and data-question messages. The domain route authorizes the parent before it creates or finalizes a transfer.

Product Attachment Response

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"product_id": "9afb13a6-7dc1-4db9-a55f-9dbf16b73ed0",
"repository_id": null,
"repository_kind": null,
"user_id": "96dd87f5-eb95-47cf-a23e-dfa8b4d3035d",
"file_name": "data_dictionary.pdf",
"file_type": "application/pdf",
"file_size_bytes": 245760,
"storage_uri": "s3://managed-location",
"storage_backend": "s3",
"created_at": "2026-08-03T11:00:00Z",
"markdown_snippet": null,
"entity_type": "product"
}

file_data and provider credentials are never included in attachment responses. Treat storage_uri as opaque and use the authorized download route.

Resumable Upload

Use the transfer protocol for new integrations. It keeps progress durable, allows safe part retries, and avoids buffering the complete file in one API request.

1. Initiate

POST /catalog/spaces/{slug}/products/{product_id}/attachments/transfers
Content-Type: application/json
{
"file_name": "data_dictionary.pdf",
"content_type": "application/pdf",
"total_size_bytes": 245760,
"sha256": "a6e8f5632ce243d4d1808683951b6d286c4e0784fa77a47b624d0fe590a50f5f"
}

The response selects sequential or multipart upload mode and reports the server-owned chunk size, parallelism limit, committed parts, revision, status, and expiry time:

{
"id": "3023f516-45bb-46f6-82db-0769cd615ca4",
"storage_family": "default",
"purpose": "attachment",
"target_kind": "product",
"target_id": "9afb13a6-7dc1-4db9-a55f-9dbf16b73ed0",
"storage_backend": "s3",
"upload_mode": "multipart",
"file_name": "data_dictionary.pdf",
"content_type": "application/pdf",
"total_size_bytes": 245760,
"chunk_size_bytes": 8388608,
"maximum_parallel_parts": 4,
"expected_sha256": "a6e8f5632ce243d4d1808683951b6d286c4e0784fa77a47b624d0fe590a50f5f",
"actual_sha256": null,
"committed_bytes": 0,
"committed_parts": [],
"revision": 0,
"status": "initialized",
"expires_at": "2026-08-04T11:00:00Z",
"created_at": "2026-08-03T11:00:00Z",
"completed_at": null
}

2. Upload Parts

Read the latest transfer state with:

GET /storage/transfers/{transfer_id}

Split the file using the returned chunk_size_bytes. Part numbers start at 1. Send the exact byte interval for each part:

PUT /storage/transfers/{transfer_id}/parts/1
Content-Type: application/octet-stream
Content-Range: bytes 0-245759/245760
Idempotency-Key: 3023f516-45bb-46f6-82db-0769cd615ca4:1
X-Chunk-SHA256: a6e8f5632ce243d4d1808683951b6d286c4e0784fa77a47b624d0fe590a50f5f

<raw bytes>

Use a stable, part-specific Idempotency-Key when retrying. After a timeout, read the transfer again before sending the part: it may already be present in committed_parts. Do not exceed maximum_parallel_parts, and upload parts in order when upload_mode is sequential.

3. Finalize The Attachment

After committed_bytes equals total_size_bytes, finalize through the same authorized domain route used to initiate the transfer:

POST /catalog/spaces/{slug}/products/{product_id}/attachments/transfers/{transfer_id}/complete
Content-Type: application/json

{
"metadata": {}
}

Finalization verifies the transfer target and owner, completes provider state, and creates the attachment record. It is idempotent: retrying a successful completion returns the same registered attachment.

Cancel an incomplete transfer with:

DELETE /storage/transfers/{transfer_id}

Transfer reads, part writes, and cancellation are owner-scoped. Provider checkpoint data and storage credentials are never returned by the API.

Storage Capabilities

Authenticated clients can inspect safe upload capabilities before initiating a domain transfer:

GET /storage/capabilities?family=default
{
"storage_family": "default",
"storage_backend": "s3",
"resumable": true,
"upload_modes": ["multipart", "sequential"],
"chunk_size_bytes": 8388608,
"maximum_parallel_parts": 4
}

Treat this response as advisory. The initiated transfer is authoritative for mode, chunk size, expiry, and parallelism. Base64 storage does not support resumable uploads; use the legacy one-request endpoint for that backend.

Legacy Multipart Upload

POST /catalog/spaces/{slug}/products/{product_id}/attachments
Content-Type: multipart/form-data
FieldTypeRequiredDescription
filefileYesFile to upload (maximum 10 MB).
curl -X POST "https://api.qarion.com/catalog/spaces/analytics/products/{id}/attachments" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@data_dictionary.pdf"

Downloading

GET /catalog/spaces/{slug}/products/{product_id}/attachments/{attachment_id}/download

The route streams file content or redirects to a provider URL, depending on storage policy. It supports HEAD and a single standard byte range such as Range: bytes=1048576-. A satisfiable proxied range returns 206 with Content-Range and Accept-Ranges: bytes; an invalid range returns 416.

Supported File Types

The approved content types include:

  • Documents: PDF, Word, Excel, PowerPoint
  • Images: PNG, JPEG, WebP
  • Data and source: CSV, JSON, XML, YAML, SQL, Python, R, JavaScript, TypeScript, shell, PowerShell, Markdown, and plain text
  • Other: Jupyter notebooks, diagrams, and generic binary content

Maximum attachment size: 10 MB.

Error Responses

StatusDescription
400Malformed legacy upload.
403Not authorized or not the transfer owner.
404Attachment, transfer, or parent entity not found.
409Transfer state, byte range, target, or idempotency conflict.
410Transfer expired.
413File or upload part is too large.
416Download byte range is not satisfiable.
422Unsupported content type, invalid metadata, or malformed Content-Range.