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’sLinkheader. 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 Portaleppn(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, ornullif 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 Portalper_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 Portaleppn(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 Portalid(path, required): The unique identifier of the passkey to delete (theidfield 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_pageout of range,offsetmalformed/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.