OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Services

swift — REST API

REST API — swift — branch stable/2026.2

Branch stable/2026.2 @ a1bdaeba21bd — repository https://opendev.org/openstack/swift — analysed on 2026-10-29

Swift has no route table. The proxy splits the path itself (swift/proxy/server.py:453) and each feature beyond plain account/container/object storage is a WSGI middleware that inspects the request on the way in and the response on the way out. This file therefore describes (1) the conventions every request follows, (2) the core account / container / object API of the proxy controllers, (3) the API added by each middleware, and (4) a short view of the internal storage-server API, which is detailed in 03-interactions_stable-2026.2.md. The machine-readable route list is _rewrite/routes_stable-2026.2.json.

1. Conventions

1.1 Paths and controller selection

PathControllerSource
/infoInfoController (capabilities)swift/proxy/server.py:462
/<version>/<account>AccountControllerswift/proxy/server.py:498
/<version>/<account>/<container>ContainerControllerswift/proxy/server.py:496
/<version>/<account>/<container>/<object> (object may contain /)ReplicatedObjectController or ECObjectController, chosen by the container's storage policyswift/proxy/server.py:495
  • version must be v1 or v1.0 (swift/common/constraints.py:40); another value with an account gives 400 (swift/proxy/server.py:476, swift/proxy/server.py:563).
  • A path that cannot be split gives 404 (swift/proxy/server.py:568); a path with no account gives 412 "Bad URL" (swift/proxy/server.py:571).
  • Invalid UTF-8 or a NUL byte in the path gives 412 "Invalid UTF8 or contains NULL" (swift/proxy/server.py:551).
  • For an object request the proxy first reads the container info (memcache, then container servers). A 5xx container lookup gives 503 (swift/proxy/server.py:480); a policy index unknown to this proxy gives 503 "Unknown Storage Policy" (swift/proxy/server.py:494). X-Backend-Storage-Policy-Index sent by a client is removed by gatekeeper before this point (see § 4.1).
  • Methods not implemented by the controller give 405 with an Allow header (swift/proxy/server.py:595). X-Backend-Allow-Private-Methods: true (internal clients only) enables UPDATE on containers (swift/proxy/server.py:592).
  • deny_host_headers gives 403 "Invalid host header" (swift/proxy/server.py:574).
  • X-Storage-Token is copied to X-Auth-Token when the latter is absent; Content-Length is dropped when Transfer-Encoding: chunked is present (request smuggling) (swift/proxy/server.py:526). A negative Content-Length gives 400 (swift/proxy/server.py:548).
  • Every response carries X-Trans-Id and X-Openstack-Request-Id (set by catch_errors, swift/common/middleware/catch_errors.py:150); X-Trans-Id-Extra from the client is appended (max 32 chars) (swift/proxy/server.py:580).

1.2 Authorization model

Authentication middlewares (tempauth, keystoneauth, s3api+s3token) set environ['swift.authorize']. The proxy calls it before the handler (swift/proxy/server.py:601). A denial is returned at once unless the handler is marked delay_denial (container/object reads), in which case the controller fetches the container ACLs first and calls the hook again. A denied request answers 401 when no identity was given and 403 otherwise (swift/common/middleware/keystoneauth.py:588, swift/common/middleware/tempauth.py:706).

ConceptRuleSource
Account name<reseller_prefix><project_id>, default prefix AUTH_swift/common/middleware/keystoneauth.py:277
Owner (keystoneauth)a user whose project matches the account and who has one of operator_roles (default admin, swiftoperator) gets swift_owner (may set ACLs, metadata, keys)swift/common/middleware/keystoneauth.py:401
Reseller adminrole reseller_admin_role (default ResellerAdmin): every account, account PUT/DELETEswift/common/middleware/keystoneauth.py:183
Service tokenwith <prefix>_service_roles, a second token in X-Service-Token is required for that reseller prefixswift/common/middleware/keystoneauth.py:116, swift/common/middleware/keystoneauth.py:251
Container ACL v1X-Container-Read / X-Container-Write: comma list of project:user, project:*, *:user, .r:<referrer> (.r:* = public), .rlistings (public listing)swift/common/middleware/acl.py:20, swift/common/middleware/acl.py:184
Account ACL v2X-Account-Access-Control JSON {"admin":[…],"read-write":[…],"read-only":[…]}, stored in account sysmeta, owner onlyswift/common/middleware/acl.py:206, swift/proxy/controllers/account.py:41
System readersystem_reader_roles: read everythingswift/common/middleware/keystoneauth.py:186

tempauth (test/dev auth, used by the functional tests) issues tokens at GET /auth/v1.0 from X-Auth-User/X-Auth-Key (or X-Storage-User/X-Storage-Pass) and answers X-Storage-Url, X-Auth-Token, X-Storage-Token, X-Auth-Token-Expires (swift/common/middleware/tempauth.py:809). Tokens are kept in memcache under <reseller_prefix>/token/<token> (swift/common/middleware/tempauth.py:797) or are Fernet-encoded when fernet_key_* is set (swift/common/middleware/tempauth.py:443).

1.3 Metadata headers

PrefixWho may set itPersisted where
X-Account-Meta-*, X-Container-Meta-*, X-Object-Meta-*client (user metadata)account/container DB metadata column (JSON), object xattrs
X-Remove-<Type>-Meta-<name>client: deletes the key (stored as empty value)idem
X-<Type>-Sysmeta-*middlewares only; stripped from client requests and from responses by gatekeeperidem
X-Object-Transient-Sysmeta-*middlewares (encryption); overwritten by every POSTobject .meta/.data xattrs
X-Backend-*internal only; stripped by gatekeeper—

Limits (swift/common/constraints.py:117, defaults overridable in swift.conf [swift-constraints], swift/common/constraints.py:48):

ConstraintDefaultError
name length of a metadata key128400 Metadata name too long: … (swift/common/constraints.py:155)
value length256400 Metadata value longer than 256: … (swift/common/constraints.py:160)
number of keys90400 Too many metadata items; max 90 (swift/common/constraints.py:165)
overall size4096400 Total metadata too large; max 4096 (swift/common/constraints.py:169)
object size5 GiB + 2 bytes (5368709122)413 Your request is too large. (swift/common/constraints.py:197)
object name1024 bytes400 Object name length of N longer than 1024 (swift/common/constraints.py:207)
account / container name256 bytes400 (swift/common/constraints.py:401)
listing limit10000412 (swift/common/constraints.py:36)
header line / request line8192400 from the WSGI server

1.4 Listings

Account and container GET return listings. Parameters: limit (≤ 10000), marker, end_marker, prefix, delimiter, reverse, path (containers, legacy), format=json|xml|plain or Accept: application/json|application/xml|text/xml|text/plain (swift/common/middleware/listing_formats.py:40). The storage servers only produce JSON; listing_formats converts it to plain text (one name per line, default) or XML (swift/common/middleware/listing_formats.py:65, swift/common/middleware/listing_formats.py:109) and hides reserved names (\x00 prefix, used by object versioning) (swift/common/middleware/listing_formats.py:124).

ListingJSON item fields
accountname, count, bytes, last_modified; with delimiter: {"subdir": "…"}
containername, hash (ETag), bytes, content_type, last_modified (ISO 8601, microseconds); symlink_path, symlink_etag, symlink_bytes for symlinks; slo_etag for SLO manifests; {"subdir": "…"} with delimiter

An account listing on a non-existent account with account_autocreate = true returns an empty listing 200/204 instead of 404 (swift/proxy/controllers/account.py:78).

1.5 Error responses

Errors are plain HTML by default: swob builds <html><h1>{title}</h1><p>{explanation}</p></html> from RESPONSE_REASONS when no body is given (swift/common/swob.py:1469, table at swift/common/swob.py:59). Most validation errors carry a short plain-text body instead (for example Invalid Content-Type). There is no JSON error body. Swift-specific statuses: 499 Client Disconnect, 507 Insufficient Storage (backend only), 529 Too Many Backend Requests (swift/common/swob.py:102, swift/common/swob.py:112, swift/common/swob.py:114). The full catalogue is 02-errors_stable-2026.2.md.

2. Discovery and utility endpoints

GET /info

  • Purpose: tells clients which features the cluster offers (swift, slo, tempurl, bulk_upload, symlink, versioned_writes, s3api, storage_policies…), with their limits. Clients rely on it: python-swiftclient decides whether to use SLO for segmented uploads (python-swiftclient:swiftclient/service.py:1529).
  • Technical: InfoController.GET (swift/proxy/controllers/info.py:42); expose_info, disallowed_sections and admin_key (swift/proxy/server.py:462). Sections registered by register_swift_info in each middleware's filter_factory. With swiftinfo_sig / swiftinfo_expires signed with admin_key, admin-only sections are added. HEAD and OPTIONS are also served. No authentication.

GET /healthcheck

Returns 200 OK body OK, or 503 DISABLED BY FILE when disable_path exists (swift/common/middleware/healthcheck.py:45). Present in the proxy and in every storage server pipeline (etc/object-server.conf-sample:118).

GET /crossdomain.xml

Flash cross-domain policy, from the cross_domain_policy option (swift/common/middleware/crossdomain.py:90).

GET /auth/v1.0 (tempauth only)

See § 1.2. Handler TempAuth.handle_get_token (swift/common/middleware/tempauth.py:809). 401 on bad credentials, 400 on missing headers.

GET /endpoints/… (list_endpoints, optional)

Returns the storage-node URLs of an account, container or object from the rings, for locality-aware clients: /endpoints/{account}/{container}/{object} (v1, list) or /endpoints/v2/… (dict with headers) (swift/common/middleware/list_endpoints.py:197). Not in the default pipeline.

3. Core API

3.1 Accounts

GET / HEAD /v1/{account}

  • Purpose: account statistics and container listing.
  • Actor: account owner, reseller admin, system reader, or users granted by the account ACL.
  • Flow: the proxy asks the account ring's primaries (then handoffs) one after the other until it gets a non-error answer (swift/proxy/controllers/account.py:51, GETorHEAD_base in swift/proxy/controllers/base.py:2300). The account server reads its SQLite DB (swift/account/server.py:225, swift/account/server.py:245).
  • Response: 204 (HEAD, or GET of an empty listing in plain format) / 200, headers X-Account-Container-Count, X-Account-Object-Count, X-Account-Bytes-Used, per policy X-Account-Storage-Policy-<name>-{Container-Count,Object-Count,Bytes-Used}, X-Timestamp, X-Put-Timestamp, user metadata, X-Account-Access-Control for owners. The result is cached in memcache and in the request environment under account/<account> (swift/proxy/controllers/base.py:651).
  • Limitations: counts are eventually consistent: they come from container servers reporting asynchronously (see the container updater in 05-async-processing_stable-2026.2.md).

POST /v1/{account}

Updates account metadata. With account_autocreate and a 404 from the backend, the proxy creates the account first, then retries (swift/proxy/controllers/account.py:154). Returns 204.

PUT / DELETE /v1/{account}

Only if allow_account_management = true (otherwise 405, swift/proxy/controllers/account.py:110, swift/proxy/controllers/account.py:163) and for a reseller admin. DELETE marks the account deleted (status = DELETED); the account reaper then deletes every container and object (swift/account/reaper.py:42). A DELETE with a query string gives 400 (swift/proxy/controllers/account.py:169).

3.2 Containers

PUT /v1/{account}/{container}

  • Purpose: create a container (or update it if it exists) and choose its storage policy.
  • Preconditions: the account exists, or account_autocreate = true (then the proxy PUTs the account first, swift/proxy/controllers/container.py:657; failure → 503). Fewer than max_containers_per_account containers unless whitelisted, else 403 (swift/proxy/controllers/container.py:664).
  • Business rules:
    • X-Storage-Policy: <name> picks the policy; an unknown or deprecated name gives 400 (swift/proxy/controllers/container.py:57). Without it the default policy is used. The policy of an existing container cannot change: the container servers answer 409 (swift/container/server.py:496).
    • ACL headers are validated and normalised (swift/proxy/controllers/container.py:78).
    • Metadata limits of § 1.3; name ≤ 256 bytes, no /.
  • Flow (synchronous): the proxy sends PUT /<dev>/<part>/<account>/<container> to every primary container node, each with the account node it must update (X-Account-Host, X-Account-Partition, X-Account-Device) (swift/proxy/controllers/base.py:1897 make_requests). Each container server creates or updates its DB, then sends PUT /<dev>/<part>/<account>/<container> to its account server (swift/container/server.py:496).
  • Outcome: 201 (created) or 202 (already existed), quorum of the container ring (swift/common/utils/__init__.py:2550).

GET / HEAD /v1/{account}/{container}

  • Listing parameters of § 1.4. Response headers X-Container-Object-Count, X-Container-Bytes-Used, X-Storage-Policy, X-Timestamp, X-Put-Timestamp, ACLs (owner only), user metadata, X-Container-Sharding (when enabled), X-Versions-Enabled…
  • Sharded containers: when a container DB has been split into shard containers (in the hidden account .shards_<account>), the proxy first gets the shard ranges (X-Backend-Record-Type: auto, states=listing, swift/proxy/controllers/container.py:203), caches them in memcache as shard-listing-v2/<account>/<container> (swift/proxy/controllers/base.py:641), then builds the listing from the shards in order (swift/proxy/controllers/container.py:443). A failing shard gives 503 (swift/proxy/controllers/container.py:572).
  • Info cached under container/<account>/<container> (swift/proxy/controllers/base.py:649), used by object requests (policy, ACLs, sync key, versioning, quotas).

POST /v1/{account}/{container}

Metadata/ACL update; 404 if absent (swift/proxy/controllers/container.py:687); returns 204.

DELETE /v1/{account}/{container}

Only an empty container can be deleted: the container servers answer 409 Conflict otherwise (swift/container/server.py:355). Returns 204. The account listing is updated asynchronously by the container servers.

3.3 Objects

PUT /v1/{account}/{container}/{object}

  • Purpose: store an object (create or overwrite atomically).
  • Preconditions: container exists (else 404); authorised to write (X-Container-Write or owner).
  • Business rules:
    • Content-Length or Transfer-Encoding: chunked required (411); Content-Type required unless X-Detect-Content-Type: true; must not contain ;swift_bytes= parameters forged by the client (400 Invalid Content-Type) (swift/common/constraints.py:175).
    • ETag (optional): MD5 of the body; mismatch → 422 (swift/obj/server.py:1019).
    • If-None-Match: * → 412 if the object exists (swift/obj/server.py:867).
    • X-Delete-At (epoch) or X-Delete-After (seconds) schedule expiry; past or non-integer → 400 (swift/common/constraints.py:309).
    • X-Object-Meta-*, Content-Disposition, Content-Encoding, X-Delete-At, X-Object-Manifest, X-Static-Large-Object and the allowed_headers of the object server are persisted (swift/obj/server.py:965).
    • X-Timestamp is set by the proxy (client value only accepted from internal callers); a newer or equal version on disk → 409 from the object server with X-Backend-Timestamp (swift/obj/server.py:874).
  • Flow:
    1. Proxy picks primaries (and handoffs for unavailable ones), opens connections with Expect: 100-continue, and gives each node the container node to update (X-Container-Host/Partition/Device) and, for expiring objects, the .expiring_objects node (X-Delete-At-*) (swift/proxy/controllers/obj.py:494).
    2. Replicated policy: the body is streamed to all nodes at once (swift/proxy/controllers/obj.py:1078). EC policy: the proxy cuts the body into segments of ec_object_segment_size (default 1 MiB, swift/common/storage_policy.py:35), encodes each segment into k+m fragments with PyECLib and streams fragment i to node i inside a MIME document, followed by a footer document (fragment index, EC etag, sizes) and a two-phase commit (swift/proxy/controllers/obj.py:2858; protocol in 03 § 2.3).
    3. Each object server writes <ts>.data (or <ts>#<frag>.data) into a temporary file, writes metadata into xattrs, fsyncs and renames it into place (swift/obj/diskfile.py:1958), then sends the container update (PUT /<dev>/<part>/<a>/<c>/<o> with X-Size, X-Etag, X-Content-Type, X-Timestamp) and waits up to container_update_timeout for it (swift/obj/server.py:363). A failed update is saved as an async pending file and retried by the object updater (swift/obj/server.py:280).
  • Outcome: 201 with ETag and Last-Modified when a quorum of nodes succeeded: floor(replicas/2)+1 for replication (swift/common/storage_policy.py:438), ec_ndata + min_parity_fragments_needed for EC (swift/common/storage_policy.py:637). Otherwise 503. The listing update may be delayed (async pending).
  • Effects elsewhere: container listing row, container byte/object counts (and later account counts), .expiring_objects queue row.

GET / HEAD /v1/{account}/{container}/{object}

  • Range (single or multiple → multipart/byteranges), If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since (swift/common/swob.py), X-Newest: true (ask every replica, keep the newest).
  • Proxy tries primaries in an order set by sorting_method (shuffle, timing, affinity, swift/proxy/server.py:102), then handoffs; EC GET needs ec_ndata fragments of the same timestamp and decodes them (swift/proxy/controllers/obj.py:2587).
  • Response headers: Content-Length, Content-Type, ETag (MD5 of the whole object; SLO: quoted MD5 of segment ETags), Last-Modified, X-Timestamp, X-Object-Meta-*, X-Delete-At, X-Static-Large-Object, X-Object-Manifest, X-Symlink-Target… An expired object (X-Delete-At past) answers 404 even before the expirer deletes it (swift/obj/diskfile.py:2800).

POST /v1/{account}/{container}/{object}

Replaces all user metadata (fast-POST): the object server writes <ts>.meta beside the .data (swift/obj/server.py:665); the proxy forwards to all primaries and returns 202. A POST may also change Content-Type, which is propagated to the container listing (content-type timestamp encoded in the .meta file name, see 03 § 4). X-Delete-At changes reschedule expiry.

DELETE /v1/{account}/{container}/{object}

Writes a tombstone <ts>.ts on every primary (swift/obj/server.py:1279) and a container listing tombstone; 204 on quorum, 404 when no replica had the object. The .data file is removed by the next cleanup of the hash directory; the tombstone itself is reclaimed after reclaim_age (default one week, swift/obj/diskfile.py:82).

COPY /v1/{account}/{container}/{object} — and PUT with X-Copy-From

Server-side copy by the copy middleware: COPY with Destination: <container>/<object> (and Destination-Account) or PUT with X-Copy-From / X-Copy-From-Account (swift/common/middleware/copy.py:320, swift/common/middleware/copy.py:395). The middleware GETs the source and PUTs the destination; metadata is copied unless X-Fresh-Metadata: true; ?multipart-manifest=get copies an SLO manifest itself. Errors: 412 on malformed Destination (swift/common/middleware/copy.py:148), 413 if the source is larger than max_file_size.

3.4 Object lifecycle

stateDiagram-v2
  [*] --> Stored: PUT (quorum of .data written)
  Stored --> Stored: POST (new .meta)
  Stored --> Stored: PUT overwrite (newer .data, older removed on cleanup)
  Stored --> Tombstoned: DELETE (.ts written)
  Stored --> Expired: X-Delete-At reached (reads give 404)
  Expired --> Tombstoned: object-expirer DELETE with X-If-Delete-At
  Tombstoned --> [*]: reclaim_age elapsed (replicator / auditor cleanup)
  Stored --> Quarantined: auditor finds bad checksum/metadata
  Quarantined --> Stored: replicator/reconstructor restores a good copy from another node

4. Middleware API

The order of the default proxy pipeline is catch_errors gatekeeper healthcheck proxy-logging cache listing_formats container_sync bulk tempurl ratelimit tempauth copy container-quotas account-quotas slo dlo versioned_writes symlink proxy-logging proxy-server (etc/proxy-server.conf-sample:160). The proxy inserts catch_errors, gatekeeper, listing_formats, copy, dlo and versioned_writes when an operator leaves them out (swift/proxy/server.py:75, swift/proxy/server.py:785). The order matters: it decides which middleware sees a request first and which error wins (07 § 2.1).

4.1 gatekeeper

Strips X-Backend-*, X-Account-Sysmeta-*, X-Container-Sysmeta-*, X-Object-Sysmeta-*, X-Object-Transient-Sysmeta-* from requests, and sysmeta from responses (swift/common/middleware/gatekeeper.py:87). Without it a client could choose a storage policy index or forge middleware state.

4.2 Static Large Objects (slo)

  • PUT …?multipart-manifest=put with a JSON list of segments [{"path": "/cont/obj", "etag": …, "size_bytes": …, "range": "…"} | {"data": "<base64>"}] (swift/common/middleware/slo.py:395). The middleware HEADs every segment, checks ETag and size, stores the manifest as an object with X-Static-Large-Object: True and sysmeta X-Object-Sysmeta-Slo-Etag / -Size (swift/common/middleware/slo.py:391).
  • Limits: max_manifest_segments 1000, max_manifest_size 8 MiB (swift/common/middleware/slo.py:380); exceeding → 413 Manifest File > … bytes (swift/common/middleware/slo.py:1362). Errors per segment are returned as a 400 body listing each bad segment.
  • heartbeat=on: the response is sent early (202) and whitespace is written every yield_frequency seconds until the real status, written in the body (swift/common/middleware/slo.py:100).
  • GET/HEAD stream the concatenated segments (swift/common/middleware/slo.py:1327); ?multipart-manifest=get returns the manifest; ?part-number=N returns one segment. DELETE …?multipart-manifest=delete deletes the manifest and its segments (bulk-delete style response).
  • ETag of an SLO: "<md5 of concatenated segment etags>" (quoted).

4.3 Dynamic Large Objects (dlo)

An object with X-Object-Manifest: <container>/<prefix> is served as the concatenation of every object listed under that prefix, in listing order (swift/common/middleware/dlo.py:245, swift/common/middleware/dlo.py:364). ETag = quoted MD5 of the segment ETags. max_get_time (86400 s) bounds a GET (swift/common/middleware/dlo.py:402). Glance's swift store writes DLOs (§ 5 of 07).

PUT with X-Symlink-Target: <container>/<object> (and optionally X-Symlink-Target-Account, X-Symlink-Target-Etag) creates a zero-byte link (swift/common/middleware/symlink.py:580). GET/HEAD follow it (up to symloop_max, default 2) (swift/common/middleware/symlink.py:445); ?symlink=get returns the link itself. Container listings show symlink_path (swift/common/middleware/symlink.py:338).

4.5 Object versioning

  • object_versioning (new style): X-Versions-Enabled: true on a container (swift/common/middleware/versioned_writes/object_versioning.py:1565). Old versions are kept in a hidden container \x00versions\x00<container> (swift/common/middleware/versioned_writes/object_versioning.py:195) and the current object becomes a symlink to the newest version (swift/common/middleware/versioned_writes/object_versioning.py:435). GET …?versions lists versions, ?version-id=<id> reads, deletes or restores one (swift/common/middleware/versioned_writes/object_versioning.py:714).
  • legacy versioned_writes: X-Versions-Location: <container> (stack mode: DELETE restores the previous version) or X-History-Location (history mode) (swift/common/middleware/versioned_writes/legacy.py:703, swift/common/middleware/versioned_writes/legacy.py:773). Version names: <len(name) as 3 hex digits><name>/<inverted timestamp> (swift/common/middleware/versioned_writes/legacy.py:428).

4.6 Temporary URLs (tempurl)

?temp_url_sig=<hmac>&temp_url_expires=<epoch or ISO 8601>[&temp_url_prefix=…][&filename=…][&inline]. HMAC body "<METHOD>\n<expires>\n<path>" (swift/common/middleware/tempurl.py:84), or "ip=<range>\n…" with temp_url_ip_range (swift/common/middleware/tempurl.py:167), keyed with X-Account-Meta-Temp-URL-Key[-2] or X-Container-Meta-Temp-URL-Key[-2] (swift/common/middleware/tempurl.py:731). Digests sha1 sha256 sha512 (swift/common/digest.py:21), hex or <alg>:<base64>. Invalid or expired → 401 (swift/common/middleware/tempurl.py:787). The request is authorised only for the object (or prefix) and method signed; headers listed in incoming_remove_headers / outgoing_remove_headers are filtered (swift/common/middleware/tempurl.py:818, swift/common/middleware/tempurl.py:840).

4.7 Form POST (formpost)

POST /v1/<account>/<container>[/<prefix>] with multipart/form-data fields redirect, max_file_size, max_file_count, expires, signature and files. Signature: HMAC (sha1/sha256/sha512) of "<path>\n<redirect>\n<max_file_size>\n<max_file_count>\n<expires>" with the temp-URL keys (swift/common/middleware/formpost.py:99, swift/common/middleware/formpost.py:429). Each file becomes a PUT subrequest (swift/common/middleware/formpost.py:342); the result is a 303 to redirect?status=…&message=… or a 201/400/401 HTML page.

4.8 Static websites (staticweb)

For containers with X-Container-Meta-Web-Index, -Web-Error, -Web-Listings, -Web-Listings-CSS, -Web-Directory-Type, anonymous (.r:*) GETs on directories are served as index pages or HTML listings (swift/common/middleware/staticweb.py:411, swift/common/middleware/staticweb.py:451). Only active when X-Web-Mode: true is sent or the request is anonymous.

4.9 Bulk operations (bulk)

  • PUT /v1/<account>[/<container>[/<prefix>]]?extract-archive=tar|tar.gz|tar.bz2: unpacks the archive into objects, creating containers as needed (swift/common/middleware/bulk.py:468). Limits max_containers_per_extraction (10000), max_failed_extractions (1000).
  • POST|DELETE …?bulk-delete with a newline-separated list of /container/object paths (swift/common/middleware/bulk.py:331), max max_deletes_per_request (10000).
  • Responses: 200 with a body (plain/JSON/XML per Accept) holding Number Files Created|Deleted, Number Not Found, Errors, Response Status; whitespace is sent while working to keep the connection alive.

4.10 Quotas

  • container_quotas: X-Container-Meta-Quota-Bytes / -Quota-Count checked on object PUT/COPY → 413 Upload exceeds quota. (swift/common/middleware/container_quotas.py:65, swift/common/middleware/container_quotas.py:76).
  • account_quotas: X-Account-Meta-Quota-Bytes, per policy X-Account-Quota-Bytes-Policy-<name>, settable by reseller admins only; exceeded → 413 (swift/common/middleware/account_quotas.py:105, swift/common/middleware/account_quotas.py:189).

4.11 Container sync (container_sync)

Containers with X-Container-Sync-To: //<realm>/<cluster>/<account>/<container> and X-Container-Sync-Key are pushed by the swift-container-sync daemon to another cluster (05). The middleware authenticates incoming sync requests with X-Container-Sync-Auth (realm key HMAC) and exposes the realms in /info (swift/common/middleware/container_sync.py:72).

4.12 Encryption at rest (keymaster + encryption)

keymaster derives per-object keys from a root secret (config, Barbican through kms_keymaster, or KMIP) with HMAC-SHA256 over the object path (swift/common/middleware/crypto/keymaster.py:63, swift/common/middleware/crypto/keymaster.py:307). encryption encrypts object bodies, user metadata and container-listing ETags with AES-256-CTR (swift/common/middleware/crypto/crypto_utils.py:46); the wrapped body key and IVs are stored in X-Object-Sysmeta-Crypto-Body-Meta, etc. (swift/common/middleware/crypto/encrypter.py:237). Storage nodes see only ciphertext. The API is unchanged for clients.

4.13 S3 compatibility (s3api)

s3api translates the Amazon S3 REST API into Swift requests (swift/common/middleware/s3api/s3api.py:212). Controllers: service (list buckets), bucket, object, multipart upload (on top of SLO and a <bucket>+segments container), multi-delete, ACL (s3_acl emulation optional), versioning (on object_versioning), tagging (stub), location, logging, object lock (swift/common/middleware/s3api/controllers/). Authentication: AWS signature v2 and v4 (header or presigned query), checked either by tempauth (S3 keys in its user config) or by s3token, which posts the string-to-sign and signature to Keystone POST /v3/s3tokens (swift/common/middleware/s3api/s3token.py:154) and maps the answer to X-Project-Id, X-Roles… (swift/common/middleware/s3api/s3token.py:117). Errors are S3 XML <Error><Code>…</Code><Message>…</Message>…</Error> (swift/common/middleware/s3api/s3response.py). Path-style and virtual-hosted-style buckets (storage_domain). The detailed S3 operation list is in _rewrite/routes_stable-2026.2.json notes and the s3api functional exchanges.

4.14 Others

MiddlewareEffectSource
ratelimitsleeps or returns 498 (Slow down) for writes to busy containers/accounts; keys ratelimit/<account>[/<container>] in memcacheswift/common/middleware/ratelimit.py:232
name_checkrejects names with forbidden characters or regex → 400swift/common/middleware/name_check.py:117
domain_remap, cname_lookupmap container.account.storage-domain host names to pathsswift/common/middleware/domain_remap.py:139, swift/common/middleware/cname_lookup.py:127
etag_quoterquotes object ETags (RFC 7232) per account/container flagswift/common/middleware/etag_quoter.py:55
read_onlycluster- or account-wide read-only (405 on writes)swift/common/middleware/read_only.py:75
proxy_loggingaccess log line per request (twice in the pipeline: client view and subrequest view)swift/common/middleware/proxy_logging.py:645
memcache (cache)puts the MemcacheRing client in environ['swift.cache']swift/common/middleware/memcache.py:30
container_sync, xprofile, backend_ratelimitsee 05 / profiling / per-device rate limit on storage servers (529)swift/common/middleware/backend_ratelimit.py:198

5. Internal storage-server API (summary)

Storage servers listen on their own ports (default 6200 object, 6201 container, 6202 account) and are called only by proxies, other storage servers and daemons. Paths are /<device>/<partition>/<account>[/<container>[/<object>]], plus REPLICATE and SSYNC verbs.

ServerMethodsSource
accountPUT, GET, HEAD, POST, DELETE, REPLICATE (JSON RPC)swift/account/server.py:128–swift/account/server.py:296
containerPUT, GET, HEAD, POST, DELETE, UPDATE (bulk shard-range/object rows), REPLICATE (JSON RPC)swift/container/server.py:355–swift/container/server.py:994
objectPUT, GET, HEAD, POST, DELETE, REPLICATE (pickled suffix hashes), SSYNC (chunked sync protocol)swift/obj/server.py:665–swift/obj/server.py:1418

REPLICATE and SSYNC are marked @replication (swift/obj/server.py:1384) and are refused on a node configured with replication_server = false; normal verbs are refused with replication_server = true. Headers, bodies and formats: 03-interactions_stable-2026.2.md § 2.

6. Gaps between documentation and code

  • The api-ref (api-ref/source/) documents the core API and several middlewares, but not UPDATE on containers, X-Backend-Allow-Private-Methods, nor the internal server API.
  • list_endpoints, cname_lookup, domain_remap, xprofile are not in the default pipeline; they are optional.
  • s3api behaviour depends on s3_acl, allow_multipart_uploads, check_bucket_owner… ([inferred] from option names; only the default configuration of the functional tests was exercised).

Source: reverse-spec-swift/01-rest-api_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.