---
title: PutObject · S3 API
description: Writes an object.
doc_version: 0.1.0-preview
last_updated: 2026-09-14
---

# PutObject

`PUT /{bucket}/{key}`

Writes an object.

Writes the request body as the object at the key. A write to a key that exists replaces the object. With versioning on, the earlier version stays. When indexing is enabled for the bucket, Graphon indexes the object for search and query.

- Role: Editor or higher.
- Support: supported.
- Auth: AWS Signature Version 4 with your API key. See [S3 API](/api/s3.md).
- One request can carry at most 200 MiB. Storage checks each checksum header that you send. If-None-Match: * creates only.
- The aws CLI and the SDKs switch to a multipart upload for a larger body by themselves.
- Storage accepts `aws-chunked` bodies, signed or unsigned, with checksum trailers.

## Example request

```http
PUT /support-tickets/a.json HTTP/1.1
Host: wspq7ws2np5l8z4.storage.beta.graphon.sh
x-amz-date: 20261007T093000Z
x-amz-content-sha256: 696df138a080281f3e11d464ed4de3f2806655f1f49b3e6b17ab0e88038ce307
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-Type: application/json
Content-Length: 34

{"ticket": 4182, "status": "open"}
```

## 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.

## Headers

- `Content-Length` (integer, required): The size of the body in bytes. One request can carry at most 200 MiB.
- `Content-Type` (string, optional): The media type to store with the object. Default: `application/octet-stream`.
- `Cache-Control` (string, optional): Stored with the object and returned on reads.
- `Content-Disposition` (string, optional): Stored with the object and returned on reads.
- `Content-Encoding` (string, optional): Stored with the object and returned on reads. For an `aws-chunked` body, storage removes `aws-chunked` and keeps the other encodings.
- `Content-Language` (string, optional): Stored with the object and returned on reads.
- `x-amz-meta-*` (string, optional): User metadata. Storage keeps each `x-amz-meta-` header with the object. GetObject and HeadObject do not return it.
- `x-amz-storage-class` (string, optional): Recorded with the object and shown in listings. It does not change how storage keeps the bytes. Default: `STANDARD`.
- `x-amz-tagging` (string, optional): Tags for the object, as a URL query. For example, `team=support&tier=gold`.
- `If-None-Match` (string, optional): Send `*` to write only when the key has no object. An object at the key returns `412 PreconditionFailed`.
- `If-Match` (string, optional): Write only when the current ETag matches. Another ETag, or no object, returns `412 PreconditionFailed`.
- `Content-MD5` (string, optional): The base64 MD5 digest of the body. A different digest returns `400 BadDigest`.
- `x-amz-checksum-*` (string, optional): A base64 checksum of the body: `crc32`, `crc32c`, `crc64nvme`, `sha1`, or `sha256`. Storage checks it, stores it, and returns it on reads. A different checksum returns `400 BadDigest`.
- `x-amz-sdk-checksum-algorithm` (string, optional): The checksum to compute: `CRC32`, `CRC32C`, `CRC64NVME`, `SHA1`, or `SHA256`. `x-amz-checksum-algorithm` does the same.
- `x-amz-decoded-content-length` (integer, optional): The object size of an `aws-chunked` body. Required with `Content-Encoding: aws-chunked` or a `STREAMING-` payload hash. Without it, storage returns `411 MissingContentLength`.
- `x-amz-expected-bucket-owner` (string, optional): The workspace ID that must own the bucket. Another owner returns `403 AccessDenied`.

## Request body

- `Body` (binary, required): The bytes of the object.

## Responses

### 200 · OK

Storage wrote the object.

Response headers:

- `ETag` (string, optional): The entity tag of the object, in double quotes.
- `x-amz-version-id` (string, optional): The version ID of the object. Storage gives every write a version ID.
- `x-amz-checksum-*` (string, optional): Each checksum that the write stored, such as `x-amz-checksum-sha256`.

```http
HTTP/1.1 200 OK
ETag: "9b2cf535f27731c974343645a3985328"
x-amz-version-id: ver_5f0c8a8e3d2b4c1e9a7f6b5d4c3b2a19
x-amz-request-id: 4F8A2C7D1E6B4093A5C2D8E1F7B3A960
```

## Errors

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

### 412 · `PreconditionFailed`

If-Match or If-None-Match did not hold.

Read the object again to get its current ETag, then decide whether to retry.

### 400 · `BadDigest`

A checksum header does not match the body. Storage did not keep the bytes.

Compute the checksum from the bytes that you send, then send the request again.

### 400 · `XAmzContentSHA256Mismatch`

The body does not match the SHA-256 that the request signed.

Hash the bytes that you send, then sign and send the request again.

### 400 · `IncompleteBody`

The body ended before the length that the request declared.

Send the whole body again.

### 411 · `MissingContentLength`

An `aws-chunked` body came without `x-amz-decoded-content-length`.

Send the object size in `x-amz-decoded-content-length`.

### 413 · `EntityTooLarge`

The body is larger than 200 MiB.

Use a multipart upload. The aws CLI and the SDKs do this for a large file by themselves.

### 503 · `SlowDown`

The bucket has too many writes in progress. The response has `Retry-After: 1`.

Wait for the Retry-After time, then send the request again. SDKs retry by themselves.

### 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

aws s3 cp ./a.json s3://support-tickets/a.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"}),
)

response = s3.put_object(
    Bucket="support-tickets",
    Key="a.json",
    Body=b'{"ticket": 4182, "status": "open"}',
    ContentType="application/json",
)
print(response["ETag"])
```

### JavaScript (AWS SDK)

```javascript
// Replace wspq7ws2np5l8z4 with your workspace ID.
import { S3Client, PutObjectCommand } 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 response = await s3.send(new PutObjectCommand({
  Bucket: 'support-tickets',
  Key: 'a.json',
  Body: JSON.stringify({ ticket: 4182, status: 'open' }),
  ContentType: 'application/json',
}));
console.log(response.ETag);
```

## Sitemap

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