---
title: CompleteMultipartUpload · S3 API
description: Joins the uploaded parts into one object.
doc_version: 0.1.0-preview
last_updated: 2026-09-14
---

# CompleteMultipartUpload

`POST /{bucket}/{key}?uploadId`

Joins the uploaded parts into one object.

Storage joins the parts that you list, in order, and writes the object. Parts that you do not list are dropped. Graphon then indexes the object like any other write.

- Role: Editor or higher.
- Support: supported.
- Auth: AWS Signature Version 4 with your API key. See [S3 API](/api/s3.md).
- Send the parts in ascending order with their exact ETags.
- A retry after a success returns the object again. So a client can send this call again after a network error.
- The object gets the Content-Type from CreateMultipartUpload, and no other stored headers.

## Example request

```http
POST /support-tickets/big.bin?uploadId=VXBsb2FkIGZvciBiaWcuYmlu HTTP/1.1
Host: wspq7ws2np5l8z4.storage.beta.graphon.sh
x-amz-date: 20261007T093000Z
x-amz-content-sha256: ee91ac69c74ba09f8c95ee3a81ca32fb1f207587bc0880cf86ed1545c297f739
Authorization: AWS4-HMAC-SHA256 Credential=<ACCESS_KEY_ID>/20261007/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=<SIGNATURE>
Content-Length: 304

<CompleteMultipartUpload xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Part>
    <PartNumber>1</PartNumber>
    <ETag>"7ac66c0f148de9519b8bd264312c4d64"</ETag>
  </Part>
  <Part>
    <PartNumber>2</PartNumber>
    <ETag>"0cc175b9c0f1b6a831c399e269772661"</ETag>
  </Part>
</CompleteMultipartUpload>
```

## Path parameters

- `bucket` (string, required): The bucket name.
- `key` (string, required): The object key. Keep each `/` as it is. Percent-encode the other reserved characters.

## Query parameters

- `uploadId` (string, required): The upload ID from CreateMultipartUpload.

## Headers

- `x-amz-expected-bucket-owner` (string, optional): The workspace ID that must own the bucket. Another owner returns `403 AccessDenied`.

## Request body

- `CompleteMultipartUpload` (object, required): The root element.
  - `Part` (array, required): One element for each part, in ascending part number order.
    - `PartNumber` (integer, required): The part number.
    - `ETag` (string, required): The ETag that UploadPart or UploadPartCopy returned.

## Responses

### 200 · OK

Storage wrote the object.

Response body:

- `CompleteMultipartUploadResult` (object, required): The root element.
  - `Location` (string, optional): The path of the object, `/{bucket}/{key}`.
  - `Bucket` (string, optional): The bucket name.
  - `Key` (string, optional): The object key.
  - `ETag` (string, optional): The entity tag of the object.

```http
HTTP/1.1 200 OK
Content-Type: application/xml
x-amz-request-id: 4F8A2C7D1E6B4093A5C2D8E1F7B3A960

<?xml version="1.0" encoding="UTF-8"?>
<CompleteMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Location>/support-tickets/big.bin</Location>
  <Bucket>support-tickets</Bucket>
  <Key>big.bin</Key>
  <ETag>"3858f62230ac3c915f300c664312c11f-2"</ETag>
</CompleteMultipartUploadResult>
```

## Errors

Any request can also get the common errors on [S3 API](/api/s3.md).

### 400 · `InvalidPartOrder`

The part list is empty, or the part numbers do not go up.

List the parts in ascending part number order.

### 400 · `InvalidPart`

A part is missing, or its ETag differs from the ETag that UploadPart returned.

Call ListParts, then send each part number with its exact ETag.

### 404 · `NoSuchUpload`

The upload ID is not known for this key. The upload was completed or aborted.

Start a new upload with CreateMultipartUpload.

### 404 · `NoSuchBucket`

No bucket has this name, or the bucket was deleted.

Check the bucket name. ListBuckets shows the buckets of the workspace.

### 403 · `AccessDenied`

The key does not have the role for this operation, or the bucket is in another workspace.

Use a key of the workspace that owns the bucket, with the role above.

## Other request languages

### aws CLI

```bash
# The graphon profile from the S3 API overview.
export AWS_PROFILE=graphon

UPLOAD_ID="VXBsb2FkIGZvciBiaWcuYmlu"  # The UploadId from create-multipart-upload
cat > parts.json <<'EOF'
{"Parts": [
  {"PartNumber": 1, "ETag": "\"7ac66c0f148de9519b8bd264312c4d64\""},
  {"PartNumber": 2, "ETag": "\"0cc175b9c0f1b6a831c399e269772661\""}
]}
EOF
aws s3api complete-multipart-upload \
  --bucket support-tickets \
  --key big.bin \
  --upload-id "$UPLOAD_ID" \
  --multipart-upload file://parts.json
```

### Python (boto3)

```python
# Replace wspq7ws2np5l8z4 with your workspace ID.
import os

import boto3
from botocore.config import Config

api_key = os.environ["GRAPHON_API_KEY"]
access_key_id = api_key.split("_", 1)[1].split(".")[0]

s3 = boto3.client(
    "s3",
    endpoint_url="https://wspq7ws2np5l8z4.storage.beta.graphon.sh",
    region_name="us-east-1",
    aws_access_key_id=access_key_id,
    aws_secret_access_key=api_key,
    config=Config(signature_version="s3v4", s3={"addressing_style": "path"}),
)

upload_id = "VXBsb2FkIGZvciBiaWcuYmlu"  # The UploadId from create_multipart_upload
response = s3.complete_multipart_upload(
    Bucket="support-tickets",
    Key="big.bin",
    UploadId=upload_id,
    MultipartUpload={
        "Parts": [
            {
                "PartNumber": 1,
                "ETag": "\"7ac66c0f148de9519b8bd264312c4d64\"",
            },
            {
                "PartNumber": 2,
                "ETag": "\"0cc175b9c0f1b6a831c399e269772661\"",
            },
        ],
    },
)
print(response["ETag"])
```

### JavaScript (AWS SDK)

```javascript
// Replace wspq7ws2np5l8z4 with your workspace ID.
import { S3Client, CompleteMultipartUploadCommand } from '@aws-sdk/client-s3';

const apiKey = process.env.GRAPHON_API_KEY;
if (!apiKey) throw new Error('Set GRAPHON_API_KEY.');
const accessKeyId = apiKey.split('_')[1].split('.')[0];

const s3 = new S3Client({
  endpoint: 'https://wspq7ws2np5l8z4.storage.beta.graphon.sh',
  region: 'us-east-1',
  forcePathStyle: true,
  credentials: { accessKeyId, secretAccessKey: apiKey },
});

const uploadId = 'VXBsb2FkIGZvciBiaWcuYmlu'; // The UploadId from CreateMultipartUpload
const response = await s3.send(new CompleteMultipartUploadCommand({
  Bucket: 'support-tickets',
  Key: 'big.bin',
  UploadId: uploadId,
  MultipartUpload: {
    Parts: [
      {
        PartNumber: 1,
        ETag: '"7ac66c0f148de9519b8bd264312c4d64"',
      },
      {
        PartNumber: 2,
        ETag: '"0cc175b9c0f1b6a831c399e269772661"',
      },
    ],
  },
}));
console.log(response.ETag);
```

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
