Sample code
Each page of the API documentation is accompanied by 💻 sample Python code.
The samples use deliberately simple helpers so that the HTTP operations are easy to read.
The Preservation API is the application-facing API of the platform. It is what the Preservation UI, iiif-builder, digitisation workflows such as Goobi, and ad hoc scripts all use. If you are integrating with the platform, this is almost certainly the only API you will call.
It understands METS, manages Deposits as working areas for assembling content, generates and runs Import Jobs that turn a Deposit into a new version of an Archival Group, and publishes an Activity Stream of what has changed. Underneath it, the Storage API writes to Fedora and OCFL; you never talk to that directly.
Sample code
Each page of the API documentation is accompanied by 💻 sample Python code.
The samples use deliberately simple helpers so that the HTTP operations are easy to read.
Throughout this documentation the Preservation API is assumed to be at https://preservation-api.example. Your platform instance will be on its own hostname. There is no API resource at the root itself; the entry points are:
| Path | What it is |
|---|---|
/repository | The root of the preserved repository: a tree of Containers, Archival Groups and Binaries. See Repository. |
/deposits | Working areas for content on its way into (or out of) the repository. See Deposits. |
/activity/archivalgroups/collection | The activity stream of created and updated Archival Groups. See Activity Stream. |
/search | Search across Deposits and preserved files. See Search. |
/whoami | Who the API thinks you are. See below. |
Every other resource is reached by following the id links in these responses.
The API is JSON over HTTP. Request bodies are sent with Content-Type: application/json; responses are JSON objects.
id property that is its own URI in the API, and a type property naming its resource type (Deposit, ArchivalGroup, Container, Binary, ImportJob, and so on). References to other resources are always fully qualified URIs, so a client can follow them without building URLs itself.id and type where they can be inferred from the request URI, and you should omit read-only properties.2025-10-03T09:55:34.3621834Z.The main resource types share these properties:
{ "id": "https://preservation-api.example/...", "type": "Deposit", // the resource type "created": "2024-03-14T14:58:46.102335Z", "createdBy": "https://preservation-api.example/agents/tom", "lastModified": "2024-03-28T12:00:00.00000Z", "lastModifiedBy": "https://preservation-api.example/agents/donald" // ... type-specific properties}| Property | Description |
|---|---|
id | URI of the resource in the API. |
type | The resource type. |
created | When the resource was created within the platform (not the original creation date of any file it represents). |
createdBy | The Agent URI of the user or API client that created it. |
lastModified | When the resource was last modified within the platform. |
lastModifiedBy | The Agent URI of who last modified it. |
Resources that live in the repository (Containers, Archival Groups and Binaries) have two kinds of name:
id URI, which is restricted to a safe set of characters: letters a-z and A-Z, digits 0-9, and (, ), -, _ and ., with % permitted only in escape sequences.name property, which may contain any UTF-8 characters. This records the original file or folder name, for example "my notes (final).doc" or a name in a non-Latin script, so that ugly or fragile URL encoding is never needed to preserve it.Often the two are the same, but they do not have to be.
Operations that run asynchronously (Import Jobs, Exports, Pipeline runs) report problems in an errors list on their result resource rather than as an HTTP error, because the failure happens after the HTTP request has returned. Each entry is an error object:
| Property | Description |
|---|---|
id | The URI of the resource the error relates to, where there is one. May be null. |
message | The error message. Always present. |
Synchronous failures use ordinary HTTP status codes: 400 for a malformed or inconsistent request, 401 for a missing or invalid token, 404 for a resource that does not exist, 409 for a conflict — a Deposit locked by someone else, or an If-Match precondition that does not hold — 410 for a resource that has been deleted (a tombstone, see Repository), and 422 for a request the API understood but cannot carry out, such as a METS file it will not rewrite.
GET /whoami returns the identity the API has resolved for the caller. It is the first thing to try once you have credentials, and it tells you what your actions will be attributed to.
GET /whoami{ "name": "my-integration", "source": "token", "appId": "5f1c...e2a9", "depositBucket": "working-bucket"}| Property | Description |
|---|---|
name | The name your actions are recorded under (in createdBy, preservedBy and so on). |
source | How that name was derived: user (a signed-in person), token (from the client’s token), header-fallback (from the X-Client-Identity header only), or unknown. |
appId | The application id from the token, when the caller authenticated with one. Null otherwise. |
depositBucket | The S3 bucket in which this caller’s Deposits are created. |
See Authentication for how to obtain a token and why X-Client-Identity matters.