Passkey administration#

This API provides endpoints for managing passkeys registered by users in your organization. You can list and delete passkeys for users in your own organization, allowing you to support user offboarding, security audits, and helpdesk support for lost or compromised devices.

The endpoints are available at https://api.feide.no/2/passkey/<org_id>

The <org_id> parameter in all endpoints is the numeric organization ID from the Feide Customer Portal.

Pagination#

The list endpoints return at most 1000 entries per response. When more entries exist, the response includes a Link header pointing to the next page:

Link: <https://api.feide.no/2/passkey/1234567?offset=...>; rel="next"

To retrieve all entries, follow the Link header URL until the response no longer contains one. The offset token in the URL is opaque, encrypted, short-lived (5 minutes), and bound to the original request URL — do not attempt to construct, modify, or reuse it across different queries.

Query parameters:

  • per_page (optional): Page size, between 1 and 1000. Defaults to 1000.

  • offset (optional): Opaque continuation token from a previous response’s Link header. Do not set this manually.

Example:

# Fetch first page
curl -i -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567"

# Response includes a "Link: <NEXT_URL>; rel=\"next\"" header when more
# entries exist. Follow it to get the next page:
curl -i -H "Authorization: Bearer JWT_TOKEN" "<NEXT_URL>"

List Passkeys for a User#

List all passkeys registered by a specific user within an organization.

Endpoint: GET /2/passkey/<org_id>?eppn=<eppn>

Required scope: passkey.read

Parameters:

  • org_id (path, required): The organization’s numeric ID from the Customer Portal

  • eppn (query, required): The user’s eduPersonPrincipalName (e.g., user@example.no). The eppn’s realm must match the organization. Note: special characters in the URL must be URL-encoded (e.g., @ as %40).

  • per_page (query, optional): See Pagination.

  • offset (query, optional): See Pagination.

Response fields:

  • id: Internal unique identifier of the passkey. The same value is used in the path of the single-passkey delete endpoint.

  • eppn: The user the passkey belongs to.

  • label: User-supplied name for the passkey (e.g., “MacBook Pro”, “YubiKey”). Free-form text; does not necessarily reflect the authenticator’s actual model.

  • created_at: When the passkey was registered (ISO 8601).

  • last_used_at: When the passkey was most recently used to authenticate, or null if never used since registration.

  • mfa_verified: Whether the passkey is verified as a multi-factor credential.

  • aaguid: The Authenticator Attestation GUID — a stable identifier for the authenticator model (e.g. a specific YubiKey series), as defined by the FIDO2 spec. The all-zero UUID "00000000-0000-0000-0000-000000000000" means the authenticator did not provide one (common for some platform authenticators). AAGUIDs identify the device type, not the individual passkey. For a community-maintained mapping from AAGUID to human-readable provider names see passkey-authenticator-aaguids.

Response:

[
  {
    "id": 1,
    "eppn": "user@example.no",
    "label": "YubiKey 5",
    "created_at": "2024-01-15T10:30:00+00:00",
    "last_used_at": "2024-03-10T14:22:33+00:00",
    "mfa_verified": true,
    "aaguid": "cb69481e-8ff7-4039-93ec-0a2729a154a8"
  },
  {
    "id": 2,
    "eppn": "user@example.no",
    "label": "Label for my passkey (set on innsyn)",
    "created_at": "2024-02-20T09:15:00+00:00",
    "last_used_at": null,
    "mfa_verified": false,
    "aaguid": "00000000-0000-0000-0000-000000000000"
  }
]

Example:

curl -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567?eppn=user%40example.no"

List All Passkeys for your Organization#

List all passkeys for all users in your organization. Results are paginated; see Pagination.

Endpoint: GET /2/passkey/<org_id>

Required scope: passkey.read

Parameters:

  • org_id (path, required): The organization’s numeric ID from the Customer Portal

  • per_page (query, optional): See Pagination.

  • offset (query, optional): See Pagination.

Response: Array of passkey objects (same format as user listing). When more entries exist, a Link: <url>; rel="next" header points to the next page. Fetch it by calling that URL exactly as returned, and repeat until a response no longer includes a Link header. The example below has only two pages for simplicity.

Example:

# First page
curl -i -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567"

# Next page: use the URL returned in the previous response's Link header
curl -i -H "Authorization: Bearer JWT_TOKEN" \
  "<Link that was returned in the Link header of the previous response>"

Delete All Passkeys for a User#

Delete all passkeys registered by a specific user.

Endpoint: DELETE /2/passkey/<org_id>?eppn=<eppn>

Required scope: passkey.delete

Parameters:

  • org_id (path, required): The organization’s numeric ID from the Customer Portal

  • eppn (query, required): The user’s eduPersonPrincipalName. The eppn’s realm must match the organization.

Response:

{
  "deleted_count": 3,
  "eppn": "user@example.no"
}

Example:

curl -X DELETE -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567?eppn=user%40example.no"

Delete a Single Passkey#

Delete a specific passkey by its unique ID.

Endpoint: DELETE /2/passkey/<org_id>/<id>

Required scope: passkey.delete

Parameters:

  • org_id (path, required): The organization’s numeric ID from the Customer Portal

  • id (path, required): The unique identifier of the passkey to delete (the id field returned by the list endpoints)

Response:

{
  "deleted": true,
  "id": 6
}

Example:

curl -X DELETE -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567/6"

Error Responses#

The API returns standard HTTP status codes with a JSON body containing code and message:

  • 200 OK: Successful operation

  • 400 Bad Request: Invalid eppn (missing @), missing required eppn parameter on DELETE, or invalid pagination parameters (per_page out of range, offset malformed/expired)

  • 401 Unauthorized: Missing or invalid JWT token

  • 403 Forbidden: Insufficient scopes, service not authorized for organization, or eppn realm does not match organization

  • 404 Not Found: Organization or passkey does not exist

  • 405 Method Not Allowed: Wrong HTTP method used

Example error responses:

{
  "code": 403,
  "message": "Token must have all required scopes"
}
{
  "code": 403,
  "message": "Client not authorized for organization"
}
{
  "code": 403,
  "message": "eppn realm 'other.example.no' does not match organization"
}
{
  "code": 404,
  "message": "Passkey does not exist"
}
{
  "code": 400,
  "message": "Invalid 'offset' query parameter -- token has expired"
}

Use Cases#

Common scenarios for using the Passkey Administration API:

User offboarding

When an employee leaves the organization, automatically remove all their passkeys so they can no longer use passkey-based login. Note that this does not revoke access through other login methods.

# Delete all passkeys for departing user
curl -X DELETE -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567?eppn=former.employee%40example.no"

Security audit

Generate reports of passkey usage across your organization. Walk through all pages by following the Link: <url>; rel="next" header:

# Fetch the first page; follow the Link header until none is returned
curl -i -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567"

Helpdesk support

Assist users who need to remove a lost or compromised device.

# List user's passkeys to find the one to remove
curl -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567?eppn=user%40example.no"

# Delete the specific passkey
curl -X DELETE -H "Authorization: Bearer JWT_TOKEN" \
  "https://api.feide.no/2/passkey/1234567/6"

For details on how to gain access to these endpoints see Feide login with Passkeys.