Let customers use Object Storage
The portal API for a customer's Object Storage buckets, objects, access keys and bucket settings.
These portal routes let a customer use the Object Storage you sold them: make buckets, browse and upload objects, issue access keys for S3 clients, and set versioning, Object Lock, CORS and lifecycle rules. They are the customer Object Storage API on a different path. A route that is /api/v1/client/object-storage/{service_id}/buckets there is /api/v1/orgs/acme/portal/object-storage/{service_id}/buckets here, with the same request body and the same answer. This page lists what differs and walks through the tasks a storefront needs most.
Before you begin
Section titled Before you begin- Get a customer token as Sign customers in to your storefront describes. Every call on this page sends it as
Authorization: Bearer $CUSTOMER_TOKEN. Only the customer who bought a service can use it. - Sell Object Storage in your catalogue, as Set up products and pricing explains. Customers order it like any other product; see Take an order.
How the portal differs from the customer API
Section titled How the portal differs from the customer API- The paths start with
/api/v1/orgs/{org_slug}/portal/object-storage.service_idis theservice_idfromGET /portal/object-storage/services. The same service isorg_service_idthere, which the billing routes take, such as Change a plan and Cancel a service. - A service, bucket or key that belongs to another customer, to another organization or to a service that has ended answers
404:Object storage service not found,Bucket not foundorAccess key not found. - Every change needs the service to be
active. A suspended service answers409withThis service is suspended; it can be changed once it is active. Reading, downloading with a presignedGETURL and revoking a key work in every status. rates,currencyandfreeare your organization's own terms for the product (Sell Object Storage): the price of storage and of each million operations incurrency, and the amounts each customer gets free every month. They are never our prices, which are what we charge your organization.ratesandfreearenullonly when the terms cannot be read, andoverage_per_gb_monthis alwaysnull.next_due_date,billing_cycleandproduct_nameare the ones on your customer's service.- Every customer has a bucket prefix of their own, such as
c42-. The service'sbucket_prefixandGET .../bucketsshow it. No other customer of yours, and no customer of another organization, can have a bucket whose name starts with it. - Each customer has these budgets of changes an hour, shared by all their services: 60 bucket changes (making and deleting), 60 key changes (issuing and revoking), 60 changes to CORS and lifecycle rules together, and 300 changes to versioning, Object Lock and versions. A change over the budget answers
429with aRetry-Afterheader. - Every change is recorded in your organization's audit log against the customer, as
object_storage.bucket_created,object_storage.key_issuedand so on. - The portal has no routes for public URLs, custom domains and static websites, event notifications, migrations from other providers, replication between regions, temporary credentials or default encryption. They are for platform accounts only.
Find the customer's services
Section titled Find the customer's servicescurl "https://api.coritan.com/api/v1/orgs/acme/portal/object-storage/services" \
-H "Authorization: Bearer $CUSTOMER_TOKEN"
The answer is {"items": [...], "total": n}, newest first, and leaves out ended services. Each item has these fields:
| Field | What it holds |
|---|---|
service_id |
The ID every other route on this page takes. |
org_service_id |
The service's ID in your organization, for the billing routes. |
label, hostname, product_name |
The names your customer sees, from your service and product. |
status |
Where the service stands, such as active or suspended. |
region, location_id |
The service's home region. |
namespace, bucket_prefix |
The customer's namespace, such as c42, and what their bucket names start with, c42-. |
bucket_count, key_count, used_bytes, object_count |
What the service holds now, as we last measured it. |
billing_model |
payg: billed by use. quota_bytes is null. |
rates, currency, free |
What your organization charges, in currency, and what each customer gets free each month. |
billing_cycle, next_due_date |
How and when your organization bills the service. |
GET /portal/object-storage/{service_id} adds endpoints (the S3 endpoint of each region the service has a bucket in), max_buckets, presign_max_seconds and grace_days. GET /portal/object-storage/regions lists the regions a bucket can go in, each with its id, code, name and endpoint.
Order Object Storage for a customer
Section titled Order Object Storage for a customerA customer orders Object Storage like any other product. List the catalogue, take the product whose module_name is object_storage, and place the order with the region in config:
curl "https://api.coritan.com/api/v1/orgs/acme/storefront/products"
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/storefront/order" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"org_product_id": 412, "org_pricing_id": 977, "hostname": "object-storage", "config": {"location_id": 3, "bucket_name": "assets"}}'
The product's metadata.object_storage holds your terms: currency, the rates for storage and each million operations, and the free amounts each customer gets every month. config.location_id is the id of a region from GET /portal/object-storage/regions. config.bucket_name is optional, and we add the customer's prefix to it.
A plan that costs nothing a month makes a $0 order. It needs no payment, and the answer has requires_payment set to false. A free order takes the checks every free order takes: turnstile_token when Turnstile is on, and the limit on free services for each customer, which answers 409 with free_limit_reached (Take an order).
The service_id in the answer is the service's ID in your organization, which is the portal's org_service_id. The portal's own routes take a different ID. Ask GET /portal/object-storage/services until an item has that org_service_id, and use the service_id of that item. The service shows in that list with a status other than active until we have set it up. Make changes once it reads active.
Make a bucket
Section titled Make a bucketcurl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/object-storage/4812/buckets" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "photos", "location_id": 3, "versioning": true}'
The answer is 201 with the bucket. name is what the customer types, 1–63 characters. We add the customer's prefix when it is missing, so this bucket is c42-photos. A customer who types another customer's prefix, or a platform account's, gets it inside their own: c2-photos becomes c42-c2-photos. The whole name is 3–63 characters in lowercase letters, digits, hyphens and dots. It starts and ends with a letter or a digit, and has no dot next to a dot or a hyphen. Leave out location_id to use the service's home region.
versioning keeps every version of every object from the start. object_lock creates the bucket with Object Lock, which a customer cannot add later: {"object_lock": {"mode": "GOVERNANCE", "days": 30}}, with days or years. Create and delete buckets and Protect objects with Object Lock explain both.
A name that is taken answers 409, as does a service that already holds max_buckets buckets. A name that breaks a rule answers 422 with the reason.
DELETE .../buckets/{bucket_id} deletes a bucket. It answers 409 while the bucket holds objects, unless the request adds ?force=true, which deletes every object and every version in it for good. A bucket with Object Lock refuses while a version is still locked. After a delete, the name stays taken for grace_days.
Browse and upload objects
Section titled Browse and upload objectsEvery path below starts with /api/v1/orgs/acme/portal/object-storage/{service_id}/buckets/{bucket_id}:
| Route | What it does |
|---|---|
GET /objects |
Lists one page under prefix, folders first. max_keys is 1–1000 and flat=true skips folders. Pass next_token back as token for the next page. |
POST /objects/presign |
Returns a URL that a browser or app uses for one request, as the next block shows. |
DELETE /objects |
Deletes up to 1000 keys, sent as {"keys": [...]}. A key that fails is listed in errors. |
Bytes go straight between the customer's browser and storage, so your storefront never carries them. Ask for a URL, then use it:
curl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/object-storage/4812/buckets/9120/objects/presign" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "photos/cat.jpg", "op": "put", "content_type": "image/jpeg", "expires": 600}'
op is get, put or delete. The answer holds url, method, headers and expires_at. Send the request with that method and every header listed, such as Content-Type. expires is 60–86400 seconds, and we cap it at the service's presign_max_seconds. For get, download=true makes the browser save the file, and version_id downloads one version. Anyone who holds the URL can use it until it expires, and we count each request made with it for the service's use. A put or delete URL needs an active service.
A browser on your storefront can use these URLs while the bucket has no CORS rules. When the customer saves CORS rules, we keep one more rule first in the bucket, coritan-dashboard, that allows your storefront's addresses (the ones we serve for you and your custom domain) to use GET, PUT, HEAD and DELETE. That rule never shows in the customer's own rule list.
Issue access keys
Section titled Issue access keyscurl -X POST "https://api.coritan.com/api/v1/orgs/acme/portal/object-storage/4812/keys" \
-H "Authorization: Bearer $CUSTOMER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"label": "deploy", "mode": "read_write"}'
The answer is 201 with the key, its secret_key (shown this once), and the endpoint and region an S3 client connects to. mode is read (read and list) or read_write. label is 1–64 characters of letters, digits, spaces, dots, hyphens and underscores, and is unique in the service. Add bucket_id to open one bucket. Without it the key opens every bucket of the customer's service, the ones made later too. A key never makes or deletes buckets. A new key works within a couple of minutes.
GET .../keys lists the service's keys without their secrets, and DELETE .../keys/{key_id} revokes one for good. A label that is already in use answers 409. Create and revoke access keys and Connect an S3 client explain what a customer does with a key.
Set versioning, Object Lock, CORS and lifecycle rules
Section titled Set versioning, Object Lock, CORS and lifecycle rulesEvery path below starts with /api/v1/orgs/acme/portal/object-storage/{service_id}/buckets/{bucket_id}:
| Route | What it does |
|---|---|
PUT /versioning |
Sends {"status": "enabled"} or {"status": "suspended"}. Versioning cannot go back to off, and a bucket with Object Lock cannot be suspended. |
GET /object-lock, PUT /object-lock |
Reads or changes the default retention of a bucket made with Object Lock: mode, with days or years. |
GET /objects/versions, GET /objects/details |
Lists every version and delete marker, and reads one version's details. |
POST /objects/restore, DELETE /objects/version |
Makes an older version current again, or deletes one version for good. |
PUT /objects/legal-hold, PUT /objects/retention |
Puts a legal hold or a retention on one version. |
GET /cors, PUT /cors, DELETE /cors |
Reads, replaces or removes the bucket's CORS rules. At most 99. |
GET /lifecycle, PUT /lifecycle, DELETE /lifecycle |
Reads, replaces or removes the bucket's lifecycle rules. At most 100. |
GET /lifecycle/runs |
Lists the latest daily runs of the rules: what each removed, and what went wrong. |
The bodies and the refusals are the customer API's, which these pages describe: Keep earlier versions of objects, Protect objects with Object Lock, Set CORS rules and Expire objects with lifecycle rules. A rule that breaks a limit answers 422 and names the rule.
Show use and requests
Section titled Show use and requestsGET .../{service_id}/usage?days=30 returns what the buckets held over time, one point for each measurement we take every hour. GET .../{service_id}/requests?days=30 returns the requests per day, 1–90 days ending today: Class A, Class B and free operations, with the bytes uploaded and downloaded. Usage and billing explains each count. The routes show use and never prices.
Troubleshooting
Section titled Troubleshooting| Answer | Cause |
|---|---|
404 Object storage service not found |
The service belongs to another customer or another organization, has ended, or is not Object Storage. |
409 This service is suspended; it can be changed once it is active |
The service is not active. Reading still works. |
409 A bucket named c42-photos already exists |
The customer already has a bucket with that name. A name deleted less than grace_days ago answers 409 too, with was deleted recently and is still being removed. |
422 with a reason |
A name, label, rule or retention broke a rule. The reason says which. |
429 |
The customer made more changes than the hourly budget allows. Wait Retry-After seconds. |
What the MCServerHost and Minehost storefronts show
Section titled What the MCServerHost and Minehost storefronts showMCServerHost and Minehost both have an Object Storage area built on these routes. A customer sees the product and your rates at /object-storage, orders it, and then finds a list of services, an overview with the endpoint and bucket prefix, a bucket list, a file browser with upload and download, access keys with the secret shown once, and a usage page. Versioning, Object Lock, CORS rules and lifecycle rules appear as labels on a bucket. These storefronts do not change them yet, though the routes above do.
Related
Section titled Related- Object Storage describes what a customer does with the service in the dashboard.
- Build the customer account area covers the service list, plan changes and cancelling.