Skip to main content

Create an access key

Creates an access key and hands it back together with its secret access key, ready for an S3 client or another program. The new key always stays within the key you call with: the same permissions or fewer, buckets among its buckets, and an object prefix inside its prefix. That makes it safe to give a program a key that mints keys - whatever it creates can never reach further than it can itself.

An Idempotency-Key header makes the request safe to retry: a retry hands back the key the first request created, and no second key is made. Idempotency has the details.

When you'd use it​

  • A CI pipeline minting a key per deploy. The pipeline holds one key with Manage access keys through the API, creates a key limited to the release bucket for the job, and deletes it when the job is done.
  • Onboarding a new service. A provisioning script gives each new service its own key, limited to its own bucket and prefix, so that no two services share a secret.
  • An agent or tool that sets itself up. A program can ask for a narrower key for one task - read-only, say, on one bucket - instead of carrying its own broad key into that task.
  • Rotating a key. A new key with the same permissions comes first, the application moves over to it, and then the old one is deleted.

Request​

POST /v1/access-keys HTTP/1.1
Host: api.strg.com
Authorization: Bearer <access key id>:<secret>
Content-Type: application/json
Idempotency-Key: 8e03978e-40d5-43e8-bc93-6894a57f9324

{
"label": "ci-uploads",
"permissions": ["objects:list", "objects:read", "objects:write"],
"bucketScope": ["my-bucket"],
"keyPrefix": "builds/"
}

Request headers​

NameRequiredDescription
AuthorizationYesBearer <access key id>:<secret> of an access key with the permission Manage access keys through the API (api:access-management). Authentication in the overview explains it
Content-TypeYesapplication/json. A parameter such as ; charset=utf-8 is fine. Any other type is answered 415 unsupported_media_type
Idempotency-KeyNo1 to 255 printable ASCII characters - a UUID works well. It can be sent bare or as a quoted string ("8e03978e-…"); the quotes are not part of the key. Idempotency explains what it does

Request body​

A JSON object of at most 64 KiB, with the fields below and no others - a field not listed here is answered 400 invalid_request.

NameTypeRequiredDescription
labelstringYesA name for the key, at most 64 characters. Leading and trailing spaces are removed. It cannot be empty, and cannot be STRG Dashboard or STRG Assistant in any capitalization - those are the names of keys the dashboard creates for itself
permissionsarray of stringsYesThe permission names, such as objects:read - Permissions lists every one. At least one, none twice, and at least one storage permission: a key holding only mcp:* or api:* permissions would have nothing to reach
bucketScopearray of strings or nullNoThe names of the buckets the key is limited to. Omitted or null: every bucket in your account, including buckets created later
keyPrefixstring or nullNoThe object key prefix the key is limited to, such as builds/ - matched literally and case-sensitively. Omitted or null: no prefix

These rules decide what a key may hold:

RuleDetail
Always includedbuckets:list and buckets:read-config are part of every key, so they are left out of permissions - they are not among the names it takes, and sending one is answered 400 invalid_request
Permissions that need anotherobject-retention:bypass-governance needs objects:delete-versions or object-retention:write; bucket-object-lock:write needs bucket-object-lock:read; mcp:access-management and mcp:skip-confirmation need mcp:enabled
Permissions that depend on the scopebuckets:write and buckets:delete need a key for every bucket (bucketScope omitted or null); buckets:list-all needs a key limited to some buckets
bucketScope1 to 100 valid bucket names, none twice. Each is a bucket you own at the time of the request: a name that does not exist yet is answered 422 unknown_bucket
keyPrefixNot empty - leaving the field out is how to ask for no prefix - at most 1,024 bytes, and no control characters

A key limited to some buckets reaches them by the bucket itself, not just its name: a bucket deleted and re-created under the same name is a new bucket, and the key no longer reaches it. Scope explains this.

And this is how the key you call with shapes the new one:

The calling keyThe new key
Holds a set of permissionsHolds only permissions from that set
Is limited to some bucketsIs limited to some of those buckets - not to every bucket
Is limited to an object prefixHas a prefix that starts with it

A request outside these is answered 403 exceeds_parent. The calling key always holds api:access-management, so the new key may hold it too.

Response​

201 Created, with the new key and its secret access key.

HeaderValue
Content-Typeapplication/json
Location/v1/access-keys/<id> - the key's own URL, for Get an access key
Idempotent-Replayedtrue when an earlier request with the same Idempotency-Key created the key. Absent otherwise
Cache-Controlno-store
{
"id": "STRGEXAMPLEKEYID0001",
"label": "ci-uploads",
"bucketScope": ["my-bucket"],
"keyPrefix": "builds/",
"permissions": ["objects:list", "objects:read", "objects:write"],
"origin": "api",
"createdAt": "2026-10-18T09:30:00.000Z",
"regions": {},
"secretAccessKey": "<secret access key>"
}
FieldTypeDescription
idstringThe access key ID - what an S3 client sends as AWS_ACCESS_KEY_ID
labelstringThe name, with leading and trailing spaces removed
bucketScopearray of strings or nullThe buckets the key reaches, by name - null for every bucket
keyPrefixstring or nullThe object key prefix the key is limited to, or null
permissionsarray of stringsThe permissions, as sent
originstringWhere the key was created: api here. The dashboard's Access Keys page tags it Created through the API
createdAtstringWhen the key was created - ISO 8601, in UTC
regionsobjectEach region where the key is set up: pending until it works there, then active. Empty in the answer to a new create; a replay shows how far the key has got by then
secretAccessKeystringThe secret access key - what an S3 client sends as AWS_SECRET_ACCESS_KEY
  • This response is the moment to store the secret access key. Only this response carries it - and, within the idempotency window, a replay of it. List access keys and Get an access key never return it. If a secret ever goes missing, a new key takes only a moment to create, and deleting the old one makes sure nobody else can use it.
  • A new key takes a moment to get ready. It works in each region where you have a bucket once it is active there. Get an access key shows regions as it changes, and When a key starts working describes what the S3 API answers meanwhile.
  • Keys stay exactly as they were created, which makes them easy to reason about: a key that worked yesterday does the same today. Different permissions, buckets or prefix simply means a new key, and the old one can then be deleted.

Idempotency​

A create that ends in a timeout, a dropped connection or a 500 may or may not have made the key. An Idempotency-Key takes the guesswork out of that: a request carrying one is remembered for 24 hours, for the key you call with, so sending it again is always safe.

Within 24 hours, from the same calling key, with the same Idempotency-KeyAnswer
The same body201 with the key the first request created - the same id and secretAccessKey - and Idempotent-Replayed: true. Nothing new is created
The same body, while the first request is still runningWaits for it, then answers as above
A different body422 idempotency_key_mismatch
The same body, after the key it created was deleted409 idempotent_key_revoked
  • A retry with the same Idempotency-Key after a timeout, a lost connection or a 500 creates at most one key, however often it is sent.
  • A create that was turned down is not remembered. After a 402 or a 422 unknown_bucket, once the cause is sorted - a card added, the bucket created - the same Idempotency-Key creates the key.
  • The same body means the same values: the same label and keyPrefix, exactly as sent, the same entries in permissions and bucketScope in any order, and null the same as an omitted field. Spacing and the order of the fields make no difference.
  • The same Idempotency-Key from another access key, or after 24 hours, is a new request.

Errors​

This table lists every error the operation can answer, so a client can tell them apart by code. Each is an application/problem+json body - Errors shows its shape.

StatuscodeWhen
400invalid_requestThe body is not valid JSON, has a missing, unknown or wrongly typed field, or breaks a rule of Request body. Or Idempotency-Key is not 1 to 255 printable ASCII characters. detail names the problem
401unauthenticatedNo valid Authorization header, or the key is unknown, deleted, has another secret, or lacks Manage access keys through the API. Every case gets the same answer, with WWW-Authenticate: Bearer realm="strg.com"
402payment_method_requiredYour account has no verified card yet. Adding one is something only a person can do, on the dashboard's Billing page - A card is required explains why
403exceeds_parentThe new key would have more access than the key you call with: a permission it lacks, a bucket outside its buckets, every bucket when it is limited to some, or a prefix outside its prefix
403account_closedThe account has been closed
409idempotent_key_revokedThe key an earlier request with this Idempotency-Key created has been deleted since
413payload_too_largeThe body is larger than 64 KiB
415unsupported_media_typeContent-Type is not application/json
422unknown_bucketbucketScope names a bucket you do not have. detail lists them
422idempotency_key_mismatchThis Idempotency-Key was already used for a different body
429rate_limitedToo many requests for this key or from this address. Retry-After gives the seconds until the next attempt is welcome
500internal_errorSomething went wrong on our side. A retry with the same Idempotency-Key is safe
503buckets_unavailableYour buckets could not be checked just now, so bucketScope could not be confirmed. Retry-After gives the seconds until a retry

Examples​

Each example creates a key limited to my-bucket and the prefix builds/ that can list, download and upload, calling with the key in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. Each one also retries a failed attempt with the same Idempotency-Key, so it never creates two keys.

IDEMPOTENCY_KEY=$(uuidgen)

curl -sS --retry 3 https://api.strg.com/v1/access-keys \
-H "Authorization: Bearer $AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"label": "ci-uploads",
"permissions": ["objects:list", "objects:read", "objects:write"],
"bucketScope": ["my-bucket"],
"keyPrefix": "builds/"
}'

--retry resends the same Idempotency-Key after a timeout, a 429, a 500 or a 503, waiting as long as Retry-After says. Running the curl command again with the same $IDEMPOTENCY_KEY gets the same key back.

View as Markdown