API reference
Buckets
S3-compatible object storage: create buckets, presign object URLs, list and delete objects, set CORS, attach to apps.
A bucket holds objects and speaks the S3 protocol. Your app reaches it in two ways: ask this API for a presigned URL per object, or attach the bucket to the app, which gives the app a key scoped to that bucket and the S3_* environment variables to use it directly. See the guide Buckets.
Attaching and detaching a bucket go through the app’s attach and detach routes (see Apps) with a body of {"bucket": "<name>", "as": "<prefix>"}.
Endpoints
Section titled “Endpoints”POST /v1/buckets: Creates a bucket. Names are unique across RytivGET /v1/buckets: The organization’s bucketsGET /v1/bucket-names/:name: Whether a bucket name is still available, and if not, whyGET /v1/buckets/:name: One bucketDELETE /v1/buckets/:name: Deletes a bucketPOST /v1/buckets/:name/presign: A time-limited URL for one object and one method (getorput)GET /v1/buckets/:name/objects: Lists objects, with a cursor for the next pageDELETE /v1/buckets/:name/objects: Deletes objects by keyGET /v1/buckets/:name/cors: The bucket’s CORS rulesPUT /v1/buckets/:name/cors: Replaces the bucket’s CORS rules. An empty list removes themGET /v1/bucket-attachments: Which bucket is attached to which app, and the environment it injected
POST /v1/buckets
Section titled “POST /v1/buckets”Creates a bucket. Names are unique across Rytiv.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused (agent-token-production-readonly).
Request body
Section titled “Request body”JSON, the shape CreateBucketRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | required | The bucket’s name. Unique across Rytiv. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/buckets" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"my-bucket"}'Response
Section titled “Response”201 with the bucket:
{ "id": "<id>", "name": "my-bucket", "orgId": "<orgId>", "backendRef": "<backendRef>", "createdAt": "2026-10-21T09:00:00Z"}| Field | Type | Present | Description |
|---|---|---|---|
id |
string | always | The bucket’s id. |
name |
string | always | |
orgId |
string | always | The organization that owns it. |
backendRef |
string | always | Where the objects live, in the storage layer’s own words; opaque. |
createdAt |
string (ISO 8601) | always | When it was created. |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
GET /v1/buckets
Section titled “GET /v1/buckets”The organization’s buckets.
Auth: API key with full access or the scope read. Every class of agent token may call it.
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/buckets" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with a list of buckets, each:
[ { "id": "<id>", "name": "my-bucket", "orgId": "<orgId>", "backendRef": "<backendRef>", "createdAt": "2026-10-21T09:00:00Z" }]| Field | Type | Present | Description |
|---|---|---|---|
id |
string | always | The bucket’s id. |
name |
string | always | |
orgId |
string | always | The organization that owns it. |
backendRef |
string | always | Where the objects live, in the storage layer’s own words; opaque. |
createdAt |
string (ISO 8601) | always | When it was created. |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
GET /v1/bucket-names/:name
Section titled “GET /v1/bucket-names/:name”Whether a bucket name is still available, and if not, why.
Auth: API key with full access or the scope read. Every class of agent token may call it.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
name |
The name to check. |
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/bucket-names/<name>" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with the answer:
{ "name": "my-app", "available": true}| Field | Type | Present | Description |
|---|---|---|---|
name |
string | always | |
available |
boolean | always | Whether you can have the name. |
reason |
taken | taken-by-you | reserved | invalid |
if set | If not: taken, taken-by-you, reserved or invalid. |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
GET /v1/buckets/:name
Section titled “GET /v1/buckets/:name”One bucket.
Auth: API key with full access or the scope read. Every class of agent token may call it.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
name |
The bucket’s name. |
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/buckets/<name>" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with the bucket:
{ "id": "<id>", "name": "my-bucket", "orgId": "<orgId>", "backendRef": "<backendRef>", "createdAt": "2026-10-21T09:00:00Z"}| Field | Type | Present | Description |
|---|---|---|---|
id |
string | always | The bucket’s id. |
name |
string | always | |
orgId |
string | always | The organization that owns it. |
backendRef |
string | always | Where the objects live, in the storage layer’s own words; opaque. |
createdAt |
string (ISO 8601) | always | When it was created. |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
DELETE /v1/buckets/:name
Section titled “DELETE /v1/buckets/:name”Deletes a bucket.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused (agent-token-production-readonly).
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
name |
The bucket’s name. |
Query parameters
Section titled “Query parameters”| Name | Description |
|---|---|
force |
Example request
Section titled “Example request”curl -X DELETE "https://api.rytiv.app/v1/buckets/<name>" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with a JSON object carrying destroyed, deletedObjects, detached.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
POST /v1/buckets/:name/presign
Section titled “POST /v1/buckets/:name/presign”A time-limited URL for one object and one method (get or put).
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
name |
The bucket’s name. |
Request body
Section titled “Request body”JSON, the shape PresignRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
op |
get | put |
optional | get for a download URL, put for an upload URL. default "get". |
key |
string | required | The object’s key. 1 to 1024 characters. |
expiresInS |
integer | optional | How long the URL stays valid, in seconds. 1 to 604800, default 900. |
contentType |
string | optional | put only: the Content-Type the upload must send. It is part of the signature. 1 to 255 characters. |
Example request
Section titled “Example request”curl -X POST "https://api.rytiv.app/v1/buckets/<name>/presign" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"key":"uploads/report.pdf"}'Response
Section titled “Response”200 with the presigned URL:
{ "url": "<url>", "method": "GET", "key": "uploads/report.pdf", "expiresAt": "2026-10-21T09:00:00Z", "requiredHeaders": { "<key>": "<value>" }}| Field | Type | Present | Description |
|---|---|---|---|
url |
string | always | The URL to call with method. |
method |
GET | PUT |
always | With path: GET (default) or POST. |
key |
string | always | The object’s key. |
expiresAt |
string (ISO 8601) | always | When the copy is deleted (a moment). Not together with ttlSeconds. |
requiredHeaders |
object of string | always | Headers the call must send exactly; a missing one breaks the signature. |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
GET /v1/buckets/:name/objects
Section titled “GET /v1/buckets/:name/objects”Lists objects, with a cursor for the next page.
Auth: API key with full access or the scope read. An agent token of class production needs prodDataRead for it; sandbox and twin may call it.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
name |
The bucket’s name. |
Query parameters
Section titled “Query parameters”| Name | Description |
|---|---|
limit |
How many objects per page. |
prefix |
Only keys that start with this. |
cursor |
The nextCursor of the previous page. |
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/buckets/<name>/objects" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with the page of objects:
{ "bucket": "my-bucket", "objects": [ { "key": "uploads/report.pdf", "sizeBytes": 0, "lastModified": "<lastModified>" } ]}| Field | Type | Present | Description |
|---|---|---|---|
bucket |
string | always | |
objects |
array of object | always | |
nextCursor |
string | if set | Hand it back as cursor to get the next page. |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
DELETE /v1/buckets/:name/objects
Section titled “DELETE /v1/buckets/:name/objects”Deletes objects by key.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused (agent-token-production-readonly).
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
name |
The bucket’s name. |
Query parameters
Section titled “Query parameters”| Name | Description |
|---|---|
key |
Example request
Section titled “Example request”curl -X DELETE "https://api.rytiv.app/v1/buckets/<name>/objects" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with a JSON object carrying bucket, deleted.
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
GET /v1/buckets/:name/cors
Section titled “GET /v1/buckets/:name/cors”The bucket’s CORS rules.
Auth: API key with full access or the scope read. Every class of agent token may call it.
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
name |
The bucket’s name. |
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/buckets/<name>/cors" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with the rules:
{ "rules": [ { "allowedMethods": [ "GET" ], "allowedOrigins": [ "<allowedOrigin>" ] } ]}| Field | Type | Present | Description |
|---|---|---|---|
rules |
array of object | always | The whole CORS configuration; a PUT replaces it. |
warnings |
array of string | if set |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
PUT /v1/buckets/:name/cors
Section titled “PUT /v1/buckets/:name/cors”Replaces the bucket’s CORS rules. An empty list removes them.
Auth: API key with full access (a key with scopes cannot call this route). An agent token of class sandbox or twin may call it; class production is refused (agent-token-production-readonly).
Path parameters
Section titled “Path parameters”| Name | Description |
|---|---|
name |
The bucket’s name. |
Request body
Section titled “Request body”JSON, the shape PutBucketCorsRequest of the contract.
| Field | Type | Required | Description |
|---|---|---|---|
rules |
array of object | required | The whole CORS configuration; a PUT replaces it. 0 to 100 items. |
Example request
Section titled “Example request”curl -X PUT "https://api.rytiv.app/v1/buckets/<name>/cors" \ -H "Authorization: Bearer $RYTIV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"rules":[{"allowedMethods":["GET"],"allowedOrigins":["<allowedOrigin>"]}]}'Response
Section titled “Response”200 with the rules as stored:
{ "rules": [ { "allowedMethods": [ "GET" ], "allowedOrigins": [ "<allowedOrigin>" ] } ]}| Field | Type | Present | Description |
|---|---|---|---|
rules |
array of object | always | The whole CORS configuration; a PUT replaces it. |
warnings |
array of string | if set |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | bad-request |
The request is malformed or a field is out of range. error names every field; issues lists them. |
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |
| 404 | not-found |
Not found, or not yours. An object of another organization answers like one that does not exist. |
GET /v1/bucket-attachments
Section titled “GET /v1/bucket-attachments”Which bucket is attached to which app, and the environment it injected.
Auth: API key with full access or the scope read. Every class of agent token may call it.
Example request
Section titled “Example request”curl -X GET "https://api.rytiv.app/v1/bucket-attachments" \ -H "Authorization: Bearer $RYTIV_TOKEN"Response
Section titled “Response”200 with a list of attachments, each:
[ { "appName": "my-app", "bucketName": "<bucketName>", "injected": [ "<injected>" ] }]| Field | Type | Present | Description |
|---|---|---|---|
appName |
string | always | |
bucketName |
string | always | |
injected |
array of string | always | The environment variables the attachment gave the app. |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 401 | unauthenticated |
No API key, or one that does not work. |
| 403 | token-scope |
The key’s scopes do not cover this route. |