16.1.1.11. Container endpoints

The container REST API manages token containers and the templates they are created from. A container groups several tokens together for joint enrollment, synchronization, rollover, and lifecycle management — for example a smartphone holding several push and OTP tokens, or a hardware key holding multiple WebAuthn credentials. See Container for the conceptual chapter.

The endpoints fall in three audiences:

  • Admin / WebUI flows — listing, creation, assignment, realm and state management, template administration. These require admin authentication and are gated by the corresponding container Container Policies (container_create, container_list, container_assign_user, …).

  • End-user self-service — a regular user with the matching user-scope policies can invoke the same endpoints on their own containers (e.g. assign a fresh smartphone to themselves, add a token to it).

  • Client device flows — the registered container itself calls POST /container/register/finalize, POST /container/register/terminate/client, POST /container/challenge, POST /container/synchronize and POST /container/rollover directly. These five endpoints are anonymous (no auth header); the request is authenticated by a cryptographic signature over a server-issued challenge that the device generated during registration.

GET /container/

Return containers, optionally filtered, paginated and sorted. Without page / pagesize all matching containers are returned at once.

For admin callers, the response is restricted to containers in realms the calling admin’s policies allow. For user callers, only the calling user’s containers are returned. The hide_container_info and hide_tokeninfo policies may strip configured info keys from the response.

Requires authentication and the policy action container_list.

Query Parameters:
  • user – filter by the username of an assigned user.

  • container_serial – filter by container serial (case-insensitive, * wildcard).

  • type – filter by container type (case-insensitive, * wildcard).

  • token_serial – filter to containers that hold a token with this serial (case-insensitive, * wildcard).

  • template – filter by the name of the template the container was created from (case-sensitive, * wildcard).

  • container_realm – filter by realm (case-insensitive, * wildcard).

  • description – filter by description (case-insensitive, * wildcard).

  • resolver – filter by the resolver of the assigned user (case-insensitive, * wildcard).

  • assignedtrue or false to limit to assigned or unassigned containers.

  • info_key – filter by container-info key (case-sensitive, * wildcard).

  • info_value – filter by container-info value (case-insensitive, * wildcard).

  • last_auth_delta – maximum age of the last authentication (e.g. 1y, 14d, 1h, 5m, 30s).

  • last_sync_delta – maximum age of the last synchronization, same format as last_auth_delta.

  • state – state the container should be in (case-insensitive, * wildcard).

  • sortby – column to sort by (serial or type).

  • sortdirasc (default) or desc.

  • pagesize – page size; omit for no pagination.

  • page – 1-indexed page number.

  • no_token1 to omit the token list from each container entry.

Status Codes:
  • 200 OK – paginated container list in result.value with containers, count, current, next, prev.

POST /container/(string: container_serial)/assign

Assign a container to a user.

Requires authentication and the policy action container_assign_user.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • user – login name of the user (required).

  • realm – realm of the user (required if the user is not in the default realm).

Status Codes:
  • 200 OKTrue on success in result.value.

POST /container/(string: container_serial)/unassign

Unassign a user from a container. If the user no longer exists in the resolver (deleted user), supply user_id together with resolver so the assignment row can still be located.

Requires authentication and the policy action container_unassign_user.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • user – login name of the user.

  • realm – realm of the user.

  • resolver – resolver of the user.

  • user_id – user id (required for users that no longer resolve through their store).

Status Codes:
  • 200 OKTrue on success in result.value.

  • 400 Bad Request – neither user nor user_id was supplied, or the supplied identification is incomplete.

POST /container/init

Create a new container.

A container can optionally be created from a template, in which case the listed tokens are also created and added to the new container in the same call. The template may be supplied either inline as a dictionary (template) or by reference to an existing template (template_name); supplying both is an error.

Requires authentication and the policy action container_create. Realm-admins are restricted to realms their policies cover; if the caller does not specify a realm, the first realm allowed by the policy is used.

JSON Parameters:
  • type – container type (e.g. smartphone, yubikey, generic). Required.

  • description – free-form description.

  • container_serial – optional unique serial. Stored case-normalized.

  • user – optional login name of an initial assignee (requires realm).

  • realm – optional realm of the assignee (requires user).

  • template – optional template definition (dict) to use for token creation.

  • template_name – optional name of an existing template to use.

Status Codes:
  • 200 OK{"container_serial": ..., "tokens": [...]} in result.value; tokens is only present when a template was used.

  • 400 Bad Request – invalid type, serial collision, or both template and template_name supplied.

DELETE /container/(string: container_serial)

Delete a container. The tokens assigned to the container are not deleted; they remain in the database and become un-attached.

Requires authentication and the policy action container_delete.

Parameters:
  • container_serial – path component, the container serial.

Status Codes:
  • 200 OKTrue on success in result.value.

  • 404 Not Found – no container with that serial exists.

POST /container/(string: container_serial)/add

Add a single token to a container.

Requires authentication and the policy action container_add_token.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • serial – serial of the token to add (required).

Status Codes:
  • 200 OKTrue on success in result.value.

POST /container/(string: container_serial)/addall

Add several tokens to a container in a single call.

Requires authentication and the policy action container_add_token.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • serial – comma-separated list of token serials (whitespace tolerated; required).

Status Codes:
  • 200 OK – dict mapping each requested serial to a per-token success boolean in result.value.

POST /container/(string: container_serial)/remove

Remove a single token from a container. The token itself is not deleted.

Requires authentication and the policy action container_remove_token.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • serial – serial of the token to remove (required).

Status Codes:
  • 200 OKTrue on success in result.value.

POST /container/(string: container_serial)/removeall

Remove several tokens from a container in a single call. The tokens themselves are not deleted.

Requires authentication and the policy action container_remove_token.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • serial – comma-separated list of token serials (whitespace tolerated; required, but may be empty when a prepolicy is configured to remove all tokens).

Status Codes:
  • 200 OK – dict mapping each requested serial to a per-token success boolean in result.value.

GET /container/tokentypes
GET /container/types

Return the container types known to this server with their descriptions and the token types each can hold. The route is available under both /container/types and the alias /container/tokentypes — same response.

Requires authentication.

Status Codes:
  • 200 OK – dict keyed by container type with description and token_types for each, in result.value.

Example response value:

{
  "smartphone": {"description": "...",
                  "token_types": ["hotp", "totp", "push", "sms"]},
  "yubikey":    {"description": "...",
                  "token_types": ["hotp", "webauthn"]}
}
POST /container/(string: container_serial)/description

Replace the free-form description of a container.

Requires authentication and the policy action container_description.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • description – new description (required).

Status Codes:
  • 200 OKTrue on success in result.value.

POST /container/(string: container_serial)/states

Set the states of a container. The full set of states is replaced by the provided list; mutually-exclusive states cancel each other out.

Requires authentication and the policy action container_state. See GET /container/statetypes for the supported states and their exclusion rules.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • states – comma-separated list of state names (whitespace tolerated; required, must be non-empty).

Status Codes:
  • 200 OK – dict mapping each requested state to whether it was set, in result.value.

GET /container/statetypes

Return the supported container states with their mutual-exclusion map. The keys are the state names; the value for each state is the list of states that the key state excludes.

Requires authentication.

Status Codes:
  • 200 OK – dict of state-exclusion lists in result.value.

POST /container/(string: container_serial)/realms

Replace the realms a container belongs to. Realms not listed in the request are removed; realms listed are added or kept. For realm-admin callers, the call is restricted to the realms the caller is allowed to manage.

Requires admin authentication and the policy action container_realms.

Parameters:
  • container_serial – path component, the container serial.

JSON Parameters:
  • realms – comma-separated list of realm names (whitespace tolerated; pass an empty string to remove all realms).

Status Codes:
  • 200 OK – dict mapping each realm to whether it was applied (plus a deleted entry counting removed realms), in result.value.

POST /container/(string: container_serial)/info/(key)

Set or update a container-info entry. If an entry with this key already exists it is overwritten — except entries marked PI_INTERNAL, which are reserved for the server and cannot be modified through this endpoint.

Requires admin authentication and the policy action container_info.

Parameters:
  • container_serial – path component, the container serial.

  • key – path component, the info key to set.

JSON Parameters:
  • value – value to store (required).

Status Codes:
  • 200 OKTrue on success in result.value.

  • 403 Forbidden – the key is reserved as PI_INTERNAL.

DELETE /container/(string: container_serial)/info/delete/(key)

Delete a container-info entry. Entries marked PI_INTERNAL are reserved for the server and cannot be removed through this endpoint.

Requires admin authentication and the policy action container_info.

Parameters:
  • container_serial – path component, the container serial.

  • key – path component, the info key to delete.

Status Codes:
  • 200 OKTrue on success in result.value.

  • 403 Forbidden – the key is reserved as PI_INTERNAL.

POST /container/register/initialize

Step 1 of container registration: prepare a container for being paired with a client device. The response carries everything the client device needs to complete the second step at POST /container/register/finalize — typically a deep link or QR code that encodes the server URL, the registration TTL, and a nonce.

Server-side parameters (server URL, challenge TTL, registration TTL, TLS verification) are injected by the container_registration_config prepolicy from the corresponding container policies; pass rollover=1 when re-pairing an already registered container with a new device.

Requires authentication. The exact policy gate depends on the rollover state — see the container_register_rollover prepolicy.

JSON Parameters:
  • container_serial – container serial (required).

  • rollover1 to initiate a rollover registration for an already-registered container; otherwise the container must not yet be registered.

Status Codes:
  • 200 OK – container-type-specific registration payload in result.value. Always includes offline_tokens listing any tokens already attached to the container for offline use.

Example response for a smartphone container:

{
  "container_url": {
    "description": "URL for privacyIDEA Container Registration",
    "img": "<QR code>",
    "value": "pia://container/SMPH0006D5BC?issuer=privacyIDEA&ttl=10..."
  },
  "nonce": "c238392af49250804c25bbd7d86408839e91fe97",
  "time_stamp": "2024-12-20T09:53:40.158319+00:00",
  "server_url": "https://pi.net",
  "ttl": 10,
  "ssl_verify": "True",
  "key_algorithm": "secp384r1",
  "hash_algorithm": "sha256",
  "offline_tokens": []
}
POST /container/register/finalize

Step 2 of container registration. Called by the client device itself after it has consumed the registration payload from POST /container/register/initialize. The exact set of parameters is container-type specific; for smartphone containers, the body typically carries the device’s public key and the signed nonce.

This endpoint is anonymous — no auth header is required. The request is authenticated by the container’s signed challenge response. Failures may be masked by the container-scope hide_specific_error_message policy.

JSON Parameters:
  • container_serial – container serial (required).

Status Codes:
  • 200 OK – container-type-specific registration result in result.value, including the policies block with client-relevant settings.

POST /container/register/(string: container_serial)/terminate

Unregister a container from the server side. The client device is left in place but will no longer be able to synchronize with the server. Use the client-side counterpart at POST /container/register/terminate/client for the device-initiated unregister.

Requires authentication and the policy action container_unregister.

Parameters:
  • container_serial – path component, the container serial.

Status Codes:
  • 200 OK{"success": <bool>} in result.value.

POST /container/register/terminate/client

Client-initiated unregister. Called by the registered container device itself when the user wants to drop the pairing from their end (e.g. removing the privacyIDEA account from the smartphone app).

This endpoint is anonymous — no auth header is required. The caller authenticates by signing a challenge that the container knows about. The container-scope policy disable_client_container_unregister (see disable_client_container_unregister) can disable this client-side path.

JSON Parameters:
  • container_serial – container serial (required).

  • signature – client’s signature over the challenge data (required).

Status Codes:
  • 200 OK{"success": <bool>} in result.value.

POST /container/challenge

Issue a fresh challenge for a registered container. The client device requests this before any operation that requires a signed response (synchronize, client-side terminate). Only registered containers (in state REGISTERED, ROLLOVER, or ROLLOVER_COMPLETED) may obtain a challenge.

This endpoint is anonymous — no auth header is required.

JSON Parameters:
  • container_serial – container serial (required).

  • scope – full URL of the operation the challenge will be bound to, e.g. https://pi.example.com/container/synchronize (required).

Status Codes:
  • 200 OK – challenge payload in result.valueserver_url, nonce, time_stamp, and any type-specific extras.

  • 400 Bad Request – container is not in a registered state.

Example response:

{
  "server_url": "https://pi.net",
  "nonce": "123456",
  "time_stamp": "2024-10-23T05:45:02.484954+00:00"
}
POST /container/synchronize

Reconcile the token list of a registered container between client and server. The client supplies its current token inventory; the server returns the diff — tokens to add (with full enroll information so the client can materialize them) and tokens to update (with the updated token details). The response also carries the container-side policies the client must honor (container_client_rollover, initially_add_tokens_to_container, disable_client_token_deletion, disable_client_container_unregister).

Tokens that the client cannot identify by serial may be identified by submitting two consecutive OTP values for HOTP, TOTP, or daypassword tokens.

Synchronization completes a rollover that was initiated via POST /container/rollover — when the registration state is ROLLOVER_COMPLETED the server flips it back to REGISTERED here.

This endpoint is anonymous — no auth header is required. The caller authenticates by signing the challenge previously obtained from POST /container/challenge.

JSON Parameters:
  • container_serial – container serial (required).

  • container_dict_client – JSON-encoded dict describing the client’s container and its tokens; see the example below.

Status Codes:
  • 200 OK – synchronization payload in result.value — see the example below; some fields are encrypted depending on the container type.

Example container_dict_client:

{
  "serial": "SMPH001",
  "type": "smartphone",
  "tokens": [
    {"serial": "TOTP001", ...},
    {"otp": ["1234", "4567"], "tokentype": "hotp"}
  ]
}

Example response:

{
  "container": {"type": "smartphone", "serial": "SMPH001"},
  "tokens": {
    "add": ["enroll_url1", "enroll_url2"],
    "update": [
      {"serial": "TOTP001", "tokentype": "totp"},
      {"serial": "HOTP001", "otp": ["1234", "9876"],
       "tokentype": "hotp", "counter": 2}
    ]
  },
  "policies": {
    "container_client_rollover": true,
    "initially_add_tokens_to_container": false,
    "disable_client_token_deletion": true,
    "disable_client_container_unregister": true
  },
  "server_url": "https://pi.net"
}
POST /container/rollover

Initiate a rollover for a registered container. Rollover generates new secrets for every token in the container and returns a fresh registration payload (deep link / QR code) the client must consume to complete the rollover via POST /container/synchronize. Used to transfer a container to a new device.

The container must be in a registered state (REGISTERED, ROLLOVER or ROLLOVER_COMPLETED).

This endpoint is anonymous — no auth header is required. The client-side container_client_rollover policy (see container_client_rollover) governs whether a client is allowed to initiate this.

JSON Parameters:
  • container_serial – container serial (required).

Status Codes:

Example response for a smartphone container:

{
  "container_url": {
    "description": "URL for privacyIDEA Container Registration",
    "img": "<QR code>",
    "value": "pia://container/SMPH0006D5BC?issuer=privacyIDEA&ttl=10..."
  },
  "nonce": "c238392af49250804c25bbd7d86408839e91fe97",
  "time_stamp": "2024-12-20T09:53:40.158319+00:00",
  "server_url": "https://pi.net",
  "ttl": 10,
  "ssl_verify": "True",
  "key_algorithm": "secp384r1",
  "hash_algorithm": "sha256",
  "passphrase_prompt": ""
}
GET /container/templates

Return container templates, optionally filtered, paginated and sorted. Without pagination parameters all matching templates are returned at once.

Requires authentication and the policy action container_template_list.

Query Parameters:
  • name – filter by template name.

  • container_type – filter by container type.

  • page – 1-indexed page number.

  • pagesize – page size; omit for no pagination.

  • sortdirasc (default) or desc.

  • sortby – column to sort by, default name.

Status Codes:
  • 200 OK – paginated dict with templates and pagination metadata in result.value.

Example response:

{
  "templates": [
    {"name": "template1", "container_type": "smartphone",
     "template_options": {"tokens": [{"type": "hotp", "genkey": true}, ...]}},
    {"name": "template2", "container_type": "yubikey", ...}
  ],
  "count": 25,
  "current": 1,
  "prev": null,
  "next": 2
}
POST /container/(string: container_type)/template/(string: template_name)

Create or update a container template. If a template with the given name already exists its template_options are overwritten; otherwise a new template is created with the specified container type.

Requires authentication and the policy action container_template_create.

Parameters:
  • container_type – path component, the container type the template applies to (e.g. smartphone, yubikey).

  • template_name – path component, the unique template name.

JSON Parameters:
  • template_options – dict carrying the template options (most importantly tokens — the list of token specs to create when a container is built from this template). Defaults to an empty dict.

  • defaultTrue to mark this template as the default for the container type.

Status Codes:
  • 200 OK{"template_id": <id>} in result.value.

  • 400 Bad Requesttemplate_options is not a dictionary.

DELETE /container/template/(string: template_name)

Delete a container template. Existing containers that were created from this template are not affected.

Requires authentication and the policy action container_template_delete.

Parameters:
  • template_name – path component, the template name.

Status Codes:
  • 200 OKTrue on success in result.value.

  • 404 Not Found – no template with that name exists.

GET /container/template/(string: template_name)/compare

Compare a template against the containers built from it. The response carries the per-container delta of missing and extra tokens compared to what the template currently specifies. Only containers the calling principal is allowed to manage (admin’s realms or user’s own) are included.

If container_serial is supplied, the comparison is limited to that single container.

Requires authentication and both the container_template_list and container_list policy actions.

Parameters:
  • template_name – path component, the template name.

Query Parameters:
  • container_serial – optional, restrict to a single container.

Status Codes:
  • 200 OK – dict keyed by container serial with the per-token diff in result.value.

Example response value:

{
  "SMPH0001": {
    "tokens": {
      "missing": ["hotp"],
      "additional": ["totp"]
    }
  }
}
GET /container/template/tokentypes

Return the container types that support templates with their descriptions and the token types each can hold. The response shape is the same as GET /container/types, but limited to template-capable container types.

Requires authentication.

Status Codes:
  • 200 OK – dict keyed by container type with description and token_types for each, in result.value.