BetterIAM
Server API

devices

Device posture tells policies which registered device a request comes from and whether an MDM or EDR finds it compliant.

Device posture tells policies which registered device a request comes from and whether an MDM or EDR finds it compliant. A browser or agent enrolls a public key once, then sends a short-lived signed proof with its requests in the x-better-iam-device header. The proof is bound to the session it travels with. Decisions check it against the enrolled key and expose request.deviceAssurance, request.deviceManaged, request.deviceCompliant, request.deviceId and request.devicePlatform. Integrations report the devices they manage and their posture, and the tenant's requirements decide which of them are compliant. Registered devices are not remembered devices ("remember this device" at sign-in): they never satisfy MFA. The guide is Device posture, and the browser and Node helper is @better-iam/client/device.

Assurance levels

Every request gets one level, weakest first:

  • none: no proof, or one that did not verify. A bad proof is never an error.
  • registered: a verified proof from an active device that no active integration manages.
  • managed: the device is reported by an active integration but misses a requirement.
  • compliant: a managed device that meets every requirement of the tenant (see configure).

A proof is a compact JWS whose protected header holds only alg, typ (device-proof+jwt) and kid (the RFC 7638 thumbprint of an enrolled key), with iat within 300 seconds of the server clock and sid equal to the id of the session that authenticated the request. The algorithm is pinned by the stored key (ES256 for EC P-256, EdDSA for Ed25519), and a header carrying key material (jwk, jku, x5u, x5c) or crit is refused. The device must be active, in the identity's home tenant, and shared or owned by the identity.

Who may call what

People manage their own devices without a permission: enroll, mine and retireMine from their own sign-in session or API key in the tenant, and check from any session of the tenant. Administrators hold iam:devices:read or iam:devices:manage on iam/devices, iam/devices/{id}, iam/devices/enrollments, iam/devices/settings and iam/devices/integrations/{id}. Every iam:devices:manage call except listEnrollments and revokeEnrollment needs a recent sign-in. Integrations call report with iam:devices:report on their own iam/devices/integrations/{id}, usually through a service account's API key.

Methods20
Serveriam.api.devices
Clientclient.devices
HTTPPOST /api/iam/devices/*
MethodWhat it doesAccess
checkReports what the device presenting this request proves: its assurance, id, platform and compliance.Credential
configureSets the requirements a managed device must meet to be compliant.Credential
createEnrollmentCreates a one-time enrollment code that binds a browser or agent key to a device, usually one an integration manages.Credential
createIntegrationAdds an MDM or EDR integration that can report devices and their posture.Credential
deleteDeletes a device with its keys and enrollment codes.Credential
deleteIntegrationDeletes an integration, optionally leaving the devices it manages unmanaged.Credential
enrollEnrolls a public key for the caller's browser or agent, creating their device or binding the key to a device with an enrollment code.Credential
getReturns one device with its enrolled keys.Credential
getSettingsReturns the tenant's compliance requirements, or the defaults until they are configured.Credential
listLists the tenant's devices, newest first, each with its compliance, assurance, key count and owner.Credential
listEnrollmentsLists the tenant's enrollment codes, newest first, without the codes themselves.Credential
listIntegrationsLists the tenant's MDM and EDR integrations by name, with the number of devices each manages.Credential
mineLists the caller's devices that are not retired, each with its compliance and assurance, the device presenting this request first.Credential
removeKeyRemoves one enrolled key from a device, such as a lost browser profile or a replaced agent.Credential
reportCreates or updates up to 500 devices with their posture, as reported by an integration, all or nothing.Credential
retireRetires a device for good, deleting its keys and pending enrollment codes.Credential
retireMineRetires one of the caller's own unmanaged devices and deletes its keys.Credential
revokeEnrollmentWithdraws an enrollment code, used or not.Credential
updateRenames a device, changes its owner, or marks it lost or active again.Credential
updateIntegrationRenames an integration, disables or enables it, or changes whether the vendor's verdict counts.Credential

check

Reports what the device presenting this request proves: its assurance, id, platform and compliance.

POST/api/iam/devices/check
client.devices.check()Credential

Used inDevice posture

  • Permission: Any session of the tenant.
  • Audited as: Not audited. It records when the device and key were last seen, at most once a minute.
  • Errors: ACCESS_DENIED for a session of another tenant.

proof is absent (no header), invalid (the proof did not verify: an unknown or removed key, another session, a clock more than five minutes off, a lost or retired device) or verified. For a verified device reasons explains why it is not compliant (not-managed, stale, not-encrypted, os-too-old and so on). Call it after sign-in to decide whether to offer enrollment.

const status = await client.devices.check({ tenantId });
// { assurance: 'registered', deviceId, platform: 'macos', managed: false, compliant: false,
//   reasons: ['not-managed'], proof: 'verified' }
Input

Prop

Type

Returns

A DeviceCheck object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/check" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.devices.check(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<DeviceCheck>

configure

Sets the requirements a managed device must meet to be compliant.

POST/api/iam/devices/configure
client.devices.configure()Credential

Used inDevice posture

  • Permission: iam:devices:manage on iam/devices/settings, and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:settings with the requirements before and after.
  • Errors: INVALID_INPUT for an unknown field, a flag that is not a boolean, an unknown platform or a minimum that is not a dotted version in minOsVersions, or a maxCheckInAgeHours outside 1 to 720; RECENT_AUTH_REQUIRED.

requireEncrypted, requireScreenLock, requireFirewall and requireEdr need the posture field reported true (unknown fails). blockJailbroken (on by default) fails a device reported jailbroken. minOsVersions sets a minimum per platform (a device with an unknown version then fails), and maxCheckInAgeHours (24 by default) fails devices whose last check-in is older. An integration with trustVendorCompliance also fails devices its vendor calls non-compliant. Fields left out keep their values; minOsVersions replaces the whole map. Compliance is worked out at decision time, so the new requirements apply to the next request.

await iam.api.devices.configure(credential, {
  tenantId,
  requireEncrypted: true,
  requireScreenLock: true,
  minOsVersions: { macos: '14.0', windows: '10.0.19045' },
  maxCheckInAgeHours: 12,
});
Input

Prop

Type

Returns

A DeviceSettingsView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/configure" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.devices.configure(
  credential: CredentialInput,
  input: {
    tenantId: string;
    requireEncrypted?: boolean;
    requireScreenLock?: boolean;
    requireFirewall?: boolean;
    requireEdr?: boolean;
    blockJailbroken?: boolean;
    minOsVersions?: Partial<Record<DevicePlatform, string>>;
    maxCheckInAgeHours?: number;
  },
): Promise<DeviceSettingsView>

createEnrollment

Creates a one-time enrollment code that binds a browser or agent key to a device, usually one an integration manages.

POST/api/iam/devices/createEnrollment
client.devices.createEnrollment()Credential

Used inDevice posture

  • Permission: iam:devices:manage on iam/devices/enrollments (and, with deviceId, on iam/devices/{id} too, since using the code adds a key to that device), and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:enrollment-create (never the code).
  • Errors: ACCESS_DENIED without iam:devices:manage on the device named by deviceId; INVALID_INPUT for an expiresInMs outside 10 minutes to 30 days, an owner who is not a person of the tenant, or an owner other than the device's; INVALID_TRANSITION (409) when the device is not active; LIMIT_EXCEEDED (409) past 1000 pending codes in the tenant; NOT_FOUND for a device outside the tenant; RECENT_AUTH_REQUIRED.

The code (biam_denr_…) appears only in this answer and is stored as a hash. It lasts 7 days unless expiresInMs says otherwise and works once. With deviceId, enrollment binds the key to that device; a device with an owner limits the code to that person, as ownerIdentityId does. Deliver the code to the machine through the MDM, where the app passes it to enroll after the person signs in.

const { code, expiresAt, enrollmentId } = await iam.api.devices.createEnrollment(credential, {
  tenantId,
  deviceId: managedDeviceId,
  ownerIdentityId: aliceId,
  expiresInMs: 24 * 3_600_000,
});
Input

Prop

Type

Returns

A DeviceEnrollmentCode object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/createEnrollment" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.devices.createEnrollment(
  credential: CredentialInput,
  input: {
    tenantId: string;
    deviceId?: string;
    ownerIdentityId?: string;
    expiresInMs?: number;
  },
): Promise<DeviceEnrollmentCode>

createIntegration

Adds an MDM or EDR integration that can report devices and their posture.

POST/api/iam/devices/createIntegration
client.devices.createIntegration()Credential

Used inDevice posture

  • Permission: iam:devices:manage on iam/devices/integrations, and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:integration-create.
  • Errors: INVALID_INPUT for an unknown vendor or field; CONFLICT (409) for a name in use (ignoring case); LIMIT_EXCEEDED (409) past 20 integrations; RECENT_AUTH_REQUIRED.

vendor is intune, jamf, kandji, workspace-one, google-endpoint, crowdstrike, sentinelone or custom. trustVendorCompliance (default true) counts the vendor's own verdict next to the tenant's requirements. Give the connector a service account allowed iam:devices:report on iam/devices/integrations/{id} for the new id, and an API key scoped to that action.

Input

Prop

Type

Returns

A DeviceIntegrationView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/createIntegration" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "name": "<name>",
  "vendor": "intune"
}'
Signature
iam.api.devices.createIntegration(
  credential: CredentialInput,
  input: {
    tenantId: string;
    name: string;
    vendor: DeviceIntegrationVendor;
    trustVendorCompliance?: boolean;
  },
): Promise<DeviceIntegrationView>

delete

Deletes a device with its keys and enrollment codes.

POST/api/iam/devices/delete
client.devices.delete()Credential
  • Permission: iam:devices:manage on iam/devices/{id}, and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:delete.
  • Errors: RECENT_AUTH_REQUIRED; NOT_FOUND.

A managed device comes back with the integration's next report, as a new record without keys. Use retire to keep it out.

Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/delete" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "deviceId": "<deviceId>"
}'
Signature
iam.api.devices.delete(
  credential: CredentialInput,
  input: { tenantId: string; deviceId: string },
): Promise<{ success: true; keysRemoved: number; enrollmentsRemoved: number }>

deleteIntegration

Deletes an integration, optionally leaving the devices it manages unmanaged.

POST/api/iam/devices/deleteIntegration
client.devices.deleteIntegration()Credential
  • Permission: iam:devices:manage on iam/devices/integrations/{id}, and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:integration-delete with the number of devices detached.
  • Errors: RESOURCE_IN_USE (409) while it manages devices, unless detach: true; NOT_FOUND; RECENT_AUTH_REQUIRED.

Detached devices keep their keys and last posture but no longer count as managed, so they drop to registered.

Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/deleteIntegration" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "integrationId": "<integrationId>"
}'
Signature
iam.api.devices.deleteIntegration(
  credential: CredentialInput,
  input: { tenantId: string; integrationId: string; detach?: boolean },
): Promise<{ success: true; detached: number }>

enroll

Enrolls a public key for the caller's browser or agent, creating their device or binding the key to a device with an enrollment code.

POST/api/iam/devices/enroll
client.devices.enroll()Credential

Used inDevice posture

  • Permission: None beyond a person's own sign-in session or API key in the tenant, with a recent sign-in; never while impersonating. The request must carry a device proof (x-better-iam-device) signed with the key being enrolled, for this session, which proves the caller holds the key.
  • Audited as: device:enroll.
  • Errors: INVALID_INPUT for a key that is not a public EC P-256 or Ed25519 JWK (private members are refused outright), a missing proof or one not made with the key for this session, an unknown platform, an invalid, used or expired enrollment code, or a caller that is not a person; CONFLICT (409) when the key is already enrolled; ACCESS_DENIED from another kind of session or another tenant, or with a code or device that belongs to someone else; IMPERSONATION_RESTRICTED; RECENT_AUTH_REQUIRED; LIMIT_EXCEEDED (409) past 20 devices per person or 20 keys per device; INVALID_TRANSITION (409) when the code's device is not active; RATE_LIMITED after 30 attempts by one person in a rate-limit window.

Without enrollmentCode the call registers a new unmanaged device owned by the caller. With a code from createEnrollment it binds the key to the code's device (then name and platform are ignored) or, when the code names no device, creates one owned by the code's person, or shared when the code names nobody. The code is used up. keyId is the key's thumbprint, the kid of its proofs. The result's device has current: true when this request already carries a valid proof from it.

import { createDeviceProver } from 'better-iam/client/device';

const prover = createDeviceProver();
const { device, keyId } = await client.devices.enroll({
  tenantId,
  name: 'Work laptop',
  platform: 'macos',
  publicKey: await prover.publicJwk(),
});
Input

A DeviceEnrollInput object:

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/enroll" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "name": "<name>",
  "platform": "other",
  "publicKey": {}
}'
Signature
iam.api.devices.enroll(
  credential: CredentialInput,
  input: DeviceEnrollInput,
): Promise<{ device: MyDevice; keyId: string }>

get

Returns one device with its enrolled keys.

POST/api/iam/devices/get
client.devices.get()Credential
  • Permission: iam:devices:read on iam/devices/{id}.
  • Audited as: iam:devices:read.
  • Errors: NOT_FOUND when the device is not in this tenant.

publicKeys lists each key's thumbprint, algorithm (ES256 or EdDSA), who enrolled it and when, and when a verified proof from it was last seen.

Input

Prop

Type

Returns

A DeviceDetail object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/get" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "deviceId": "<deviceId>"
}'
Signature
iam.api.devices.get(
  credential: CredentialInput,
  input: { tenantId: string; deviceId: string },
): Promise<DeviceDetail>

getSettings

Returns the tenant's compliance requirements, or the defaults until they are configured.

POST/api/iam/devices/getSettings
client.devices.getSettings()Credential
  • Permission: iam:devices:read on iam/devices/settings.
  • Audited as: iam:devices:read.

configured is false while the tenant uses the defaults: nothing required except that jailbroken devices fail, and a check-in within 24 hours.

Input

Prop

Type

Returns

A DeviceSettingsView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/getSettings" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.devices.getSettings(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<DeviceSettingsView>

list

Lists the tenant's devices, newest first, each with its compliance, assurance, key count and owner.

POST/api/iam/devices/list
client.devices.list()Credential
  • Permission: iam:devices:read on iam/devices.
  • Audited as: iam:devices:read.
  • Errors: INVALID_INPUT for an unknown status or platform, a limit outside 1 to 1000, or an offset outside 0 to 1000000.

Filter by ownerIdentityId, status, platform, managed and compliant, and search with query (device id, name, serial number, model, the integration's device id, and the owner's name or email, case-insensitive). total counts every match before paging. assurance is what a proof from the device would get now: none while it has no key or is not active.

Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/list" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.devices.list(
  credential: CredentialInput,
  input: {
    tenantId: string;
    ownerIdentityId?: string;
    status?: DeviceStatus;
    managed?: boolean;
    compliant?: boolean;
    platform?: DevicePlatform;
    query?: string;
    limit?: number;
    offset?: number;
  },
): Promise<{ devices: DeviceView[]; total: number }>

listEnrollments

Lists the tenant's enrollment codes, newest first, without the codes themselves.

POST/api/iam/devices/listEnrollments
client.devices.listEnrollments()Credential
  • Permission: iam:devices:manage on iam/devices/enrollments.
  • Audited as: iam:devices:manage.

Each entry has its status (pending, used or expired), the device and person it is for, and who created and used it. The retention sweep deletes codes after they expire.

Input

Prop

Type

Returns

An array of DeviceEnrollmentView.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/listEnrollments" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.devices.listEnrollments(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<DeviceEnrollmentView[]>

listIntegrations

Lists the tenant's MDM and EDR integrations by name, with the number of devices each manages.

POST/api/iam/devices/listIntegrations
client.devices.listIntegrations()Credential
  • Permission: iam:devices:read on iam/devices/integrations.
  • Audited as: iam:devices:read.
Input

Prop

Type

Returns

An array of DeviceIntegrationView.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/listIntegrations" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.devices.listIntegrations(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<DeviceIntegrationView[]>

mine

Lists the caller's devices that are not retired, each with its compliance and assurance, the device presenting this request first.

POST/api/iam/devices/mine
client.devices.mine()Credential
  • Permission: None beyond a person's own sign-in session or API key in the tenant; also while impersonating.
  • Audited as: Not audited; it only reads.
  • Errors: ACCESS_DENIED from a role session, session token, delegated session, or a session of another tenant.

current marks the device whose proof this request carries. Use it for a "your devices" page.

Input

Prop

Type

Returns

An array of MyDevice.

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/mine" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.devices.mine(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<MyDevice[]>

removeKey

Removes one enrolled key from a device, such as a lost browser profile or a replaced agent.

POST/api/iam/devices/removeKey
client.devices.removeKey()Credential
  • Permission: iam:devices:manage on iam/devices/{id}, and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:key-remove.
  • Errors: NOT_FOUND when the key is not enrolled on that device; RECENT_AUTH_REQUIRED.
Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/removeKey" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "deviceId": "<deviceId>",
  "keyId": "<keyId>"
}'
Signature
iam.api.devices.removeKey(
  credential: CredentialInput,
  input: { tenantId: string; deviceId: string; keyId: string },
): Promise<{ success: true }>

report

Creates or updates up to 500 devices with their posture, as reported by an integration, all or nothing.

POST/api/iam/devices/report
client.devices.report()Credential
  • Permission: iam:devices:report on iam/devices/integrations/{id}; no recent sign-in, so a service account's API key can call it.
  • Audited as: iam:devices:report; device:report once per call with the counts; and device:compliance-change for each existing device whose compliance flipped.
  • Errors: INVALID_INPUT for an empty list or more than 500 devices, a missing, malformed or repeated externalId, an unknown platform or field, a posture value that is not a boolean, an osVersion over 64 characters, a malformed keyThumbprint, or a checkedInAt more than five minutes ahead; INVALID_TRANSITION (409) when the integration is disabled; NOT_FOUND when the integration is not in this tenant.

Devices are matched by the vendor's externalId and created on first sight. null and empty strings count as absent. Each report replaces the device's posture, so a field left out becomes unknown. A named owner (ownerIdentityId or ownerEmail, a person of the tenant) is set, an unknown one leaves the device without an owner, and a report that names none keeps the current one. osVersion keeps its leading dotted number (14.5 (23F79) reads as 14.5). keyThumbprint moves a key already enrolled in the tenant onto the reported device, retiring the same person's self-enrolled record of the machine when that record has no keys left. It moves only from an active device of the reported owner that is self-enrolled or this integration's own: a key on a lost, shared, or someone else's device, or on a device another integration manages, stays where it is. Reports older than a device's last check-in are ignored, identical check-ins are written at most once a minute (always when they change the device's compliance), and reports never change a device's status. The answer counts created, updated, unchanged and complianceChanged devices.

await iam.api.devices.report(
  { token: reporterKey },
  {
    tenantId,
    integrationId,
    devices: [
      {
        externalId: 'jamf-42',
        name: 'MBP-ALICE',
        platform: 'macos',
        serialNumber: 'C02XYZ123',
        osVersion: '14.5 (23F79)',
        ownerEmail: 'alice@acme.test',
        posture: { compliant: true, encrypted: true, firewall: true, screenLock: true },
      },
    ],
  },
);
Input

Prop

Type

Returns

A DeviceReportResult object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/report" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "integrationId": "<integrationId>",
  "devices": [
    {
      "externalId": "<externalId>",
      "platform": "other",
      "posture": {}
    }
  ]
}'
Signature
iam.api.devices.report(
  credential: CredentialInput,
  input: { tenantId: string; integrationId: string; devices: DeviceReport[] },
): Promise<DeviceReportResult>

retire

Retires a device for good, deleting its keys and pending enrollment codes.

POST/api/iam/devices/retire
client.devices.retire()Credential
  • Permission: iam:devices:manage on iam/devices/{id}, and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:retire with self: false.
  • Errors: INVALID_TRANSITION (409) when it is already retired; RECENT_AUTH_REQUIRED; NOT_FOUND.

A retired device proves nothing, and later integration reports keep it retired.

Input

Prop

Type

Returns

A DeviceView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/retire" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "deviceId": "<deviceId>"
}'
Signature
iam.api.devices.retire(
  credential: CredentialInput,
  input: { tenantId: string; deviceId: string },
): Promise<DeviceView>

retireMine

Retires one of the caller's own unmanaged devices and deletes its keys.

POST/api/iam/devices/retireMine
client.devices.retireMine()Credential
  • Permission: None beyond a person's own sign-in session or API key in the tenant, with a recent sign-in; never while impersonating.
  • Audited as: device:retire with self: true.
  • Errors: NOT_FOUND when the device is not the caller's; INVALID_TRANSITION (409) for a managed device (an administrator retires those) or one already retired; IMPERSONATION_RESTRICTED; RECENT_AUTH_REQUIRED; ACCESS_DENIED from another kind of session.
Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/retireMine" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "deviceId": "<deviceId>"
}'
Signature
iam.api.devices.retireMine(
  credential: CredentialInput,
  input: { tenantId: string; deviceId: string },
): Promise<{ success: true; keysRemoved: number }>

revokeEnrollment

Withdraws an enrollment code, used or not.

POST/api/iam/devices/revokeEnrollment
client.devices.revokeEnrollment()Credential
  • Permission: iam:devices:manage on iam/devices/enrollments.
  • Audited as: iam:devices:manage, plus device:enrollment-revoke.
  • Errors: NOT_FOUND when the code is not in this tenant.
Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/revokeEnrollment" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "enrollmentId": "<enrollmentId>"
}'
Signature
iam.api.devices.revokeEnrollment(
  credential: CredentialInput,
  input: { tenantId: string; enrollmentId: string },
): Promise<{ success: true }>

update

Renames a device, changes its owner, or marks it lost or active again.

POST/api/iam/devices/update
client.devices.update()Credential
  • Permission: iam:devices:manage on iam/devices/{id}, and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:update with the name, status and owner before and after.
  • Errors: INVALID_INPUT without a name, owner or status to change, for an unknown field, a status other than active or lost, or an owner who is not a person of the tenant; INVALID_TRANSITION (409) for a retired device; RECENT_AUTH_REQUIRED; NOT_FOUND.

ownerIdentityId: null makes the device shared, so any member of the tenant may present it. A new owner drops the keys the previous owner enrolled. A lost device proves nothing until it is marked active again.

Input

Prop

Type

Returns

A DeviceView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/update" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "deviceId": "<deviceId>"
}'
Signature
iam.api.devices.update(
  credential: CredentialInput,
  input: {
    tenantId: string;
    deviceId: string;
    name?: string;
    ownerIdentityId?: string | null;
    status?: 'active' | 'lost';
  },
): Promise<DeviceView>

updateIntegration

Renames an integration, disables or enables it, or changes whether the vendor's verdict counts.

POST/api/iam/devices/updateIntegration
client.devices.updateIntegration()Credential
  • Permission: iam:devices:manage on iam/devices/integrations/{id}, and a recent sign-in.
  • Audited as: iam:devices:manage, plus device:integration-update with the settings before and after.
  • Errors: CONFLICT (409) for a name in use; INVALID_INPUT for a status other than active or disabled, or an unknown field; NOT_FOUND; RECENT_AUTH_REQUIRED.

While an integration is disabled its devices do not count as managed, and its reports are refused.

Input

Prop

Type

Returns

A DeviceIntegrationView object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/devices/updateIntegration" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "integrationId": "<integrationId>"
}'
Signature
iam.api.devices.updateIntegration(
  credential: CredentialInput,
  input: {
    tenantId: string;
    integrationId: string;
    name?: string;
    status?: 'active' | 'disabled';
    trustVendorCompliance?: boolean;
  },
): Promise<DeviceIntegrationView>

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page