Documentation/API Reference
REST API

API Reference

Complete REST API documentation for Identity Mesh. Programmatically manage connectors, sync policies, and monitor identity synchronization operations.

GET /connectors
POST /connectors
GET /mesh/objects
POST /mesh/objects/search
GET /instances
POST /connectors/:id/import
GET /rules
POST /rules

Authentication

The Admin API uses Windows Negotiate (Kerberos/NTLM) by default, and also accepts JWT / Entra ID bearer tokens via the SmartAuth policy scheme. Requests must come from an authenticated principal with appropriate permissions. See the Authentication runbook.

Negotiate + Bearer (SmartAuth)

The API authenticates via Windows Negotiate (Kerberos/NTLM) by default — domain-joined clients authenticate automatically with no keys to manage. The SmartAuth policy scheme also accepts JWT / Entra ID bearer tokens, so cloud-native clients can present an OAuth token instead.

  • Negotiate by default (Kerberos preferred, NTLM fallback)
  • JWT / Entra ID bearer via SmartAuth
  • Role-based access control (RBAC)
  • Authentication runbook for setup

Example Request

curl --negotiate -u : \
  https://your-server:5100/api/connectors

Base URL

https://your-server:5100/api

The API runs on your on-premises server alongside the sync engine.

Connectors

Manage identity source and target connectors.

GET/connectors

List all configured connectors

Response

{
  "connectors": [
    {
      "id": "conn_abc123",
      "name": "Active Directory",
      "type": "active_directory",
      "status": "active",
      "lastSync": "2026-01-01T18:30:00Z"
    }
  ],
  "total": 1
}
GET/connectors/:id

Get details for a specific connector

Parameters

idConnector ID
POST/connectors

Create a new connector

Request Body

{
  "name": "Active Directory",
  "type": "active_directory",
  "config": {
    "host": "ldap.company.com",
    "port": 636,
    "baseDN": "dc=company,dc=com",
    "bindDN": "cn=admin,dc=company,dc=com",
    "bindPassword": "********"
  }
}

config must be a JSON object. A config that does not parse, or whose root is not an object, is refused with 400 and anerror naming the JSON path that carries the fault (for example $.Partitions[0].AttributesToLoad[1]), so a pasted config with a stray line-feed fails at save time instead of on the next import.

PUT/connectors/:id

Update connector configuration. The same 400 config-validation rule as create applies; masked secret values in the submitted config are restored from the stored one.

POST/connectors/discover-classes

Read the Active Directory forest schema and return each object class with its required and optional attributes. Backs the per-type attribute picker in the AD connector form. New in this release.

Request Body

configJsonThe AD connector config to bind with (credentials may be a secret reference)
partitionIdOptional partition whose credentials to use

Requires connectors.write. A bind or LDAP failure is returned assuccess: false with an errorMessage, not as an HTTP error.

DELETE/connectors/:id

Delete a connector

Rules

Create and manage sync rules — join rules, flow rules, projection rules, and MeshComposer rules.

GET/rules

List all rules, optionally filtered by connector or rule type

Response

{
  "items": [
    {
      "ruleId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "ruleName": "Join on employeeId",
      "ruleType": "JoinRule",
      "connectorId": "conn_abc123",
      "priority": 1,
      "isEnabled": true
    }
  ],
  "totalCount": 1
}
POST/rules

Create a new rule (JoinRule, FlowRule, ProjectionRule, or MeshComposerRule)

Request Body

{
  "ruleName": "Flow displayName from AD",
  "ruleType": "FlowRule",
  "connectorId": "conn_abc123",
  "sourceAttribute": "displayName",
  "targetAttribute": "displayName",
  "confidence": 90,
  "transformScript": "Trim(source)"
}

Import & Export Operations

Trigger and monitor import and export operations on connectors.

POST/connectors/:id/import

Trigger a full or delta import on a connector

Response

{
  "runId": "run_123abc",
  "connectorId": "conn_abc123",
  "runType": "DeltaImport",
  "status": "Running",
  "startedAt": "2026-01-01T19:00:00Z"
}
POST/connectors/:id/export

Trigger an export to write pending changes to a target connector

GET/runs/:id

Get status of an import or export run

Response

{
  "runId": "run_123abc",
  "connectorId": "conn_abc123",
  "runType": "DeltaImport",
  "status": "Completed",
  "startedAt": "2026-01-01T19:00:00Z",
  "completedAt": "2026-01-01T19:02:15Z",
  "stats": {
    "objectsProcessed": 1250,
    "objectsCreated": 45,
    "objectsUpdated": 203,
    "objectsDeleted": 12,
    "errors": 0
  }
}

Engine Instances

Manage sync engine instances — list, pause, resume, rename, and remove.

GET/instances

List all registered engine instances with effective status

POST/instances/:id/pause

Pause a specific engine instance

POST/instances/:id/resume

Resume a paused engine instance

PUT/instances/:id/rename

Rename an instance. Cascades across connector assignments, run history, and pause settings. Returns 409 if the new name is already taken.

DELETE/instances/:id

Remove an offline instance and unassign its connectors

PUT/connectors/:id/instance

Assign or unassign a connector to an instance. A connector already assigned to another instance must be unassigned first (409 Conflict).

Mesh Objects

Query the unified identity mesh. List, search, and inspect mesh objects with their consolidated attributes from all connected sources.

GET/mesh/objects

List mesh objects with pagination, type filtering, and anchor search

Parameters

skipNumber of records to skip (pagination offset)
takeNumber of records to return (max 200)
objectTypeFilter by object type (optional)
searchFilter by anchor value substring (optional)

Response

{
  "items": [
    {
      "meshObjectId": "c51d176b-0c9e-4b59-80bd-cb49078d9044",
      "objectType": "User",
      "anchorValue": "jsmith@company.com",
      "createdOn": "2026-01-15T10:30:00Z",
      "updatedOn": "2026-02-01T14:22:00Z",
      "attributeValues": {
        "displayName": "John Smith",
        "department": "Engineering",
        "title": "Senior Developer",
        "mail": "jsmith@company.com"
      }
    }
  ],
  "totalCount": 1,
  "skip": 0,
  "take": 25
}

Each object includes an attributeValues dictionary containing all consolidated attribute name/value pairs, enabling dynamic column display without additional API calls.

POST/mesh/objects/search

Advanced search with attribute-based criteria

Request Body

{
  "objectType": "User",
  "anchorSearch": "smith",
  "criteria": [
    {
      "attributeName": "department",
      "operator": "equals",
      "value": "Engineering"
    }
  ],
  "skip": 0,
  "take": 25
}

Supported operators: equals,contains,startswith,endswith,ispresent,isnotpresent,istrue,isfalse. Multiple criteria are combined with AND logic.

GET/mesh/objects/:id

Get full mesh object detail with attributes and relationships

Response

{
  "meshObjectId": "c51d176b-0c9e-4b59-80bd-cb49078d9044",
  "objectType": "User",
  "anchorValue": "jsmith@company.com",
  "createdOn": "2026-01-15T10:30:00Z",
  "updatedOn": "2026-02-01T14:22:00Z",
  "attributes": [
    {
      "attributeName": "displayName",
      "attributeValue": "John Smith",
      "source": "14d98031-df8d-45cd-8476-1d6dcc02407e",
      "confidence": 100,
      "updatedOn": "2026-02-01T14:22:00Z"
    }
  ],
  "relationships": [
    {
      "relationshipType": "manager",
      "targetMeshObjectId": "a23b456c-...",
      "targetAnchorValue": "mjones@company.com",
      "targetObjectType": "User"
    }
  ]
}

The detail view returns the full attribute array (with source connector, confidence score, and timestamp per attribute) and all relationships to other mesh objects.

Self-service Groups

Owner-managed groups with join requests, expiry, dual-control approval for high-impact groups, and a per-tenant policy. New in this release; see the Self-service groups runbook. Permissions: groups.read, groups.request,groups.own, groups.admin,groups.audit.read, groups.notify.read. Holding groups.own grants nothing on an existing group; ownership is per group.

Group lifecycle

Create, read, update, delete, renew and restore. A deleted or expired group's name stays reserved until retention frees it, and the refusal names that date.

GET/groups
List groups visible to the caller. groups.read
GET/groups/me
Who the caller is: sign-in name, resolved mesh person, groups.admin standing, and the mesh groups they are a direct member of (owner standing through a group owner). groups.read
POST/groups
Create a group within policy. The caller becomes primary owner; an administrator with no mesh person may name the owners instead. groups.own · rate limited
GET/groups/:id
Get a group. Per-connector provisioning state is not embedded here; read /targets. groups.read
PATCH/groups/:id
Update display name, description, join policy, high-impact flag, Entra sync, target OU, renewal period or expiry. Send rowVersion for optimistic concurrency. groups.own
DELETE/groups/:id
Delete a group. Its name stays reserved until retention frees it. groups.admin · rate limited
POST/groups/:id/renew
Extend an expiring group. groups.own
POST/groups/:id/restore
Restore an expired group before retention deletes it. groups.own · rate limited
GET/groups/:id/targets
Provisioning state per target connector. groups.read

Members and owners

Owners manage members and co-owners. Any member may leave.

GET/groups/:id/members
List members. groups.read
POST/groups/:id/members/:personId
Add a member. groups.own
DELETE/groups/:id/members/:personId
Remove a member. groups.own
DELETE/groups/:id/members/me
Leave a group. groups.request
GET/groups/:id/owners
List owners; each row carries ownerKind (Person or Group). groups.read
POST/groups/:id/owners/:personId
Add a co-owner: a person, or a mesh group whose direct members then manage the group. groups.own
DELETE/groups/:id/owners/:personId
Remove a co-owner. groups.own
GET/groups/people
Search mesh people to add as members or owners; includeGroups=true adds mesh groups (kind: Group) for the owner picker. groups.own

Join requests and approvals

A high-impact group needs two approvals: an owner first, then a different groups.admin person. Denials are exempt, and an administrator may deny at any stage.

POST/groups/:id/requests
Request to join a group. groups.request · rate limited
GET/groups/requests/mine
The caller’s own requests. groups.request
GET/groups/requests/pending
Requests awaiting an owner the caller owns for. groups.own
GET/groups/requests/pending-admin
High-impact requests awaiting the second, administrator approval. groups.admin
POST/group-requests/:id/approve
Approve a request (owner first; a different administrator second for high-impact groups). groups.own
POST/group-requests/:id/deny
Deny a request. Denials are exempt from dual control. groups.own
POST/group-requests/:id/cancel
Cancel the caller’s own request. groups.request

Policy and audit

The policy is one row. An empty creator-group list means administrators only; an empty allowed-type list means every group type.

GET/groups/policies
Read the self-service policy. groups.admin
PUT/groups/policies
Replace the whole policy row. Every field must be sent; an omitted field is not preserved. groups.admin · rate limited
GET/groups/audit
Query the group audit trail. groups.audit.read
GET/groups/audit/export.csv
The same query as a CSV download. groups.audit.read
GET/groups/onboarding/candidates
Imported, linked, not-yet-onboarded AD groups on one connector. Query connectorId (required), search, skip, take. groups.admin
POST/groups/onboarding/preview
Per group: how many members carry over, every member the first export would remove with its reason, hybrid and managedBy flags. Body { groupMeshObjectIds[] }. groups.admin
POST/groups/onboarding
Onboard each listed group independently with owners and policy for the batch. Drops need acceptedDroppedCounts per group; an empty-membership group needs acceptEmptyMembershipFor. The response lists success or refusal per group. groups.admin · rate limited
POST/groups/:id/release
Undo an onboarding: removes the self-service rows, edges, markers, pending exports and snapshots, cancels pending requests. AD membership is not touched. Refused for groups the portal created. groups.admin · rate limited

Roles & assignments

Roles are named permission sets. An assignment row names a sign-in, or a directory group whose members all hold the role at sign-in; a user row always wins over a group row. SeeAuthentication → Role rows.

GET/roles
List role names. roles.read
POST/roles
Create a role. roles.write
DELETE/roles/:roleName
Delete a role and its permission rows. roles.write
GET/roles/:roleName/permissions
List a role's permissions. roles.read
PUT/roles/:roleName/permissions
Replace a role's permissions. roles.write
GET/user-roles
List role assignments; each row carries principalType (User or Group), groups first. roles.read
GET/user-roles/principals
The Assign Role picker: live mesh people (kind=User) or groups (kind=Group) matching ?search, each with the sign-in name a row for it must carry, in the caller's own shape. roles.write
POST/user-roles
Assign a role to a sign-in, or to a directory group whose members all hold it (principalType: "Group"); one row per kind and name. roles.write
PUT/user-roles/:id
Change the role of an assignment. roles.write
DELETE/user-roles/:id
Remove an assignment; the caller falls back to the config mapping or the default role. roles.write

Secret provider

Choose, test, migrate to and switch the secret provider. Every route needs secrets.manage, held by Admin by default. Every action that changes something is audited.

GET/secrets/status
Non-secret provider settings, a status row per host (provider, version, build error, per-host secrets present, last test per role and machine), the candidate, and the last migration summary. Not audited. secrets.manage
PUT/secrets/target
Save the candidate provider and its connection settings. Does not switch. A vault endpoint is accepted on a host only when that host’s appsettings sanction it. secrets.manage
POST/secrets/test
Probe the candidate from the API host and ask every live engine to probe it on its next poll. Writes, reads back and deletes one random secret. secrets.manage
POST/secrets/migrate
Copy every catalogued secret from the active provider to the candidate and verify each by reading it back. Deletes nothing and does not switch. Returns the result document. secrets.manage
POST/secrets/switch
Cut over to the candidate. Refused unless every entry verified, every live host has a fresh successful test, and every migrated secret still matches its source byte for byte. secrets.manage
POST/secrets/purge
Delete the old provider’s copies of verified entries. A secret is deleted only when old and active copies are byte-equal; the rest are reported as diverged. The body names the provider to purge. secrets.manage

Notifications & Email

Every notification is written to the recipient's inbox, then attempted as email through the provider you choose (SMTP, Mailgun or Microsoft 365). Email is off until it is switched on and saved. See the Email runbook.

GET/me/notifications
The caller’s inbox. groups.notify.read
POST/me/notifications/:id/read
Mark one notification read. groups.notify.read
GET/settings/email
The saved email settings. Secrets are never returned, only whether one is stored. roles.read
PUT/settings/email
Save the email settings. The body carries enabled, the active provider (smtp, mailgun or m365) and one card per provider. A blank or masked secret keeps the stored one. Answers 400 with the missing fields named. roles.write
DELETE/settings/email/:provider/secret
Clear one provider's stored secret. Returns 204, or 409 when that provider is active and email is on. roles.write
POST/settings/email/test
Send a fixed test message through the saved settings. Body { "to": "address" }; returns { ok, message }. Answers 400 when email is off. roles.write
POST/client-logs
Batched browser-side errors from the Admin Portal, written to the IdentityMesh event log under the Web UI source. signed-in user · rate limited

Maintenance

Export re-evaluation and mesh-orphan cleanup, both new in this release. The older uniqueness re-resolve, duplicate-orphan cleanup and staging gates are in the Maintenance operations runbook.

POST/connectors/:id/export-sweep
Ask the engine to re-evaluate this connector’s exports from the stored Management Space without an import. Returns 202 with the status body; only genuine drift is queued. sync.execute
GET/connectors/:id/export-sweep
Status of the last requested sweep: scanned, queued, or the error. runs.read
GET/admin/cleanup-mesh-orphans/report
CSV of unlinked mesh objects that duplicate a linked object of the same type, with the match rule or the reason each is left for review. Read-only. rules.write · rate limited
POST/admin/cleanup-mesh-orphans
Apply the cleanup. Body { objectType, dryRun }; dryRun defaults to true. Run it while the engine is quiet. rules.write · rate limited
GET/admin/cleanup-unlinked-mesh-objects/report
CSV of every unlinked mesh object of one type, duplicate or not, with the hold reason for those left alone (exported snapshots, Composer-created, self-service-created). objectType is required. Read-only. rules.write · rate limited
POST/admin/cleanup-unlinked-mesh-objects
Apply the cleanup. Body { objectType (required), dryRun }; dryRun defaults to true. A blank type is a 400, never every type. rules.write · rate limited

Export sweep status

{
  "requested": false,
  "requestedUtc": null,
  "lastResult": {
    "requestedUtc": "2026-09-23T14:00:00Z",
    "startedUtc": "2026-09-23T14:00:07Z",
    "completedUtc": "2026-09-23T14:03:41Z",
    "scanned": 50210,
    "queued": 37,
    "error": null
  }
}

requested is true while a sweep is waiting for the engine to pick it up. The POST returns the same shape with202 Accepted. Queue items created by a sweep carry the reason ManagementSpaceSweep.

Mesh-orphan cleanup

The report is a CSV with columns MeshObjectId, ObjectType, AnchorValue, DisplayName, Source, DuplicateOfMeshObjectId, MatchedBy, ReviewReason, ExportedSnapshotCount. A display-name-only match, an ambiguous match, or an object with exported snapshots is reported with a ReviewReasonand never deleted. Every deletion writes an audit row naming the survivor and the match rule.

Vault: one-time credential link

A check-out session releases its credential once. These two routes let the requester hand that single release to someone, or something, without a portal account. New in this release; see the Vault operations runbook.

POST/vault/sessions/:id/credential-token
Mint a single-use credential token for an active, unviewed session. Requester only (403 otherwise); 409 names the refusal (closed, expired, already viewed, already linked). vault.credential.read · rate limited
POST/vault/credential/redeem
Redeem the token, sent in the body as { token }, and receive the credential once. Every miss answers 404. anonymous · rate limited

Issue response

{
  "token": "…",
  "redeemUrl": "https://your-server:5100/api/vault/credential/redeem",
  "expiresUtc": "2026-09-23T15:00:00Z"
}

Redeem response

{
  "credential": "…",
  "expires_utc": "2026-09-23T15:00:00Z"
}

Issuing a link spends the session's single view, so the portal's Show password on that session then returns409. The token expires with the session. It is sent in the request body, never in the URL, so request logs cannot capture it.

Migration

Saved migration projects for the Microsoft Identity Manager (MIM) migration analyzer. The decisions route changed in this release.

PUT/migration/projects/:id/decisions
Save operator decisions and re-project the assessment. 400 with a reason when the decision set cannot be projected; 409 when the project changed elsewhere. schema.write

Body: decisions, provenance andexpectedUpdatedUtc. An unprojectable decision set now returns400 with { "error": "…" }instead of a bare 500, and nothing is persisted. A missing stored export returns 400 withexportMissing: true.

API Notes

The Admin API runs on-premises alongside your sync engine. There are no external rate limits, but operator-scale routes (marked rate limited in the route tables above) share one rate limiter for sensitive operations and answer 429 when it trips.

All responses return standard JSON with consistent pagination
HTTPS required — the installer binds the API to port 5100 by default; the port and certificate are chosen during install
CORS is enabled for the Admin UI origin

Error Codes

Standard HTTP status codes with detailed error messages.

400Bad Request

Invalid request parameters

401Unauthorized

Authentication failed (Negotiate or bearer)

403Forbidden

Authenticated, but missing the route's permission, or acting on another operator's vault session

404Not Found

Resource does not exist

409Conflict

State refuses the change: a second credential view, a name already taken, a project changed elsewhere

429Too Many Requests

Rate limit exceeded

500Internal Server Error

Server-side error occurred

Connector SDK

Build custom connectors using the IIdentityConnector SDK to integrate any identity source.

IIdentityConnector

.NET interface

Implement FullImport(), DeltaImport(), and Export() to connect any identity source.

Active Directory

Built-in connector

LDAP-based connector with uSNChanged delta detection, group membership sync, and OU-aware operations.

SQL Database

Built-in connector

Configurable SQL queries for import, configurable delta columns, and parameterized export statements.

Ready to Get Started?

Request API access and start building with Identity Mesh today.