Skip to content
Coritan Docs

Let customers use Object Storage

The portal API for a customer's Object Storage buckets, objects, access keys and bucket settings.

View as Markdown

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.

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_id is the service_id from GET /portal/object-storage/services. The same service is org_service_id there, 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 found or Access key not found.
  • Every change needs the service to be active. A suspended service answers 409 with This service is suspended; it can be changed once it is active. Reading, downloading with a presigned GET URL and revoking a key work in every status.
  • rates, currency and free are your organization's own terms for the product (Sell Object Storage): the price of storage and of each million operations in currency, and the amounts each customer gets free every month. They are never our prices, which are what we charge your organization. rates and free are null only when the terms cannot be read, and overage_per_gb_month is always null. next_due_date, billing_cycle and product_name are the ones on your customer's service.
  • Every customer has a bucket prefix of their own, such as c42-. The service's bucket_prefix and GET .../buckets show 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 429 with a Retry-After header.
  • Every change is recorded in your organization's audit log against the customer, as object_storage.bucket_created, object_storage.key_issued and 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.
Shell
curl "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 customer

A 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:

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

Shell
curl -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.

Every 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:

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

Shell
curl -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 rules

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

GET .../{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.

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 show

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

API operations on this page

MethodPathWhat it does
GET/api/v1/orgs/{org_slug}/portal/object-storage/regionsThe regions where a bucket can be made, with each region's S3 endpoint
GET/api/v1/orgs/{org_slug}/portal/object-storage/servicesList services
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}One service with its endpoints and its limits
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/bucketsList buckets
POST/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/bucketsMake a bucket in the chosen region, or the service's home region
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}One bucket with its size, object count, rule counts, versioning and Object Lock
DELETE/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}Delete a bucket
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/corsGet bucket cors
PUT/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/corsReplace the bucket's CORS rules with the ones sent, at most 99
DELETE/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/corsRemove every CORS rule from the bucket
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/lifecycleThe bucket's lifecycle rules: what we delete from it, and when
PUT/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/lifecycleReplace the bucket's lifecycle rules with the ones sent, at most 100
DELETE/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/lifecycleRemove every lifecycle rule from the bucket, the default one included
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/lifecycle/runsList lifecycle runs
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/object-lockWhether the bucket has Object Lock, and the default retention it gives every new version
PUT/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/object-lockChange the default retention of a bucket that has Object Lock
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objectsOne page of the bucket under prefix, folders first
DELETE/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objectsDelete up to 1000 named objects
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objects/detailsGet object details
PUT/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objects/legal-holdPut a legal hold on a version, or take it off
POST/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objects/presignA short-lived URL a browser or app uses directly for one GET, PUT or DELETE of one object
POST/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objects/restoreRestore object version
PUT/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objects/retentionGive a version a retention, or change the one it has
DELETE/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objects/versionDelete one version or delete marker for good
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/objects/versionsList object versions
PUT/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/buckets/{bucket_id}/versioningTurn versioning on for a bucket, or suspend it
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/keysThe service's access keys
POST/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/keysIssue an access key for S3 clients
DELETE/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/keys/{key_id}Revoke an access key
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/requestsRequests to the service per day, for the last days days
GET/api/v1/orgs/{org_slug}/portal/object-storage/{service_id}/usageStored bytes and objects over time, as we measured them each hour