---
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.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, and GetObject and HeadObject return it. A key is lowercase letters, digits, periods, underscores, or hyphens. A value is printable ASCII of at most 1,024 bytes, and all metadata takes at most 2,048 bytes; otherwise the write fails with 400 InvalidArgument.
- `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.

### 403 · `QuotaExceeded`

The workspace stores twice its plan's storage limit, so storage refuses writes. Reads, lists, and deletes still work.

Delete objects, or ask an Admin to upgrade the plan. Then send the write again.

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