Credential Synchronization
Credential Synchronization automatically turns users from Database Provider, LDAP Active Directory, API Authentication Provider, and OIDC Provider (Keycloak only) sources into Credential records, either on a schedule or on manual trigger. This keeps credentials centrally up to date from the external source instead of being created manually for each provider.
Sync Profile
These common fields live in a Sync Profile section of each provider's edit screen: a separate tab for Database Provider, API Authentication Provider, and OIDC Provider (Synchronization for OIDC); for LDAP Active Directory, a Synchronization card with a Configure button that opens them in a drawer — see LDAP Active Directory.

| Field | Description |
|---|---|
| Enable Synchronization | When enabled, credentials are synchronized from this source according to the configured schedule. When disabled, neither the scheduled nor the manual synchronization runs. |
| Cron Expression | A Quartz cron expression that determines how often synchronization runs (e.g. 0 0 2 * * ?). It is optional. If left empty, synchronization can only be triggered manually from the Synchronization screen. While the field is empty it shows a grey hint text, which is not a value. |
| Deactivation Mode | The action applied to credentials — and, when group synchronization is on, to organizations — whose corresponding user or group is no longer present in the source on the last run: • Disable (recoverable) — the credential is disabled but its record is kept. This is the default. • Delete (recoverable) — access is cut the same way it always was, but the record itself is no longer physically deleted: a credential is disabled and retired in place, an organization is retired in place with the bond to its child credentials preserved. Permanent cleanup of retired records is planned as a separate maintenance capability. • None (keep as is) — no action is taken; drift is accepted. |
Delete is not the same thing as the Tombstone scope exit policy, even though both retire a record in place rather than removing it. Tombstone leaves the record's access untouched — it only marks it; Delete still cuts access, exactly as it did before this version, it just no longer removes the row while doing so.
The option is still labelled Delete (irreversible) on the dropdown itself — the label has not caught up with the change described above yet. The behavior above is the current one regardless of that label.
Provider-specific additional fields are described below.
LDAP Active Directory
| Field | Description |
|---|---|
| Base DN (from the connection) | Read-only. Synchronization reads users and groups under the Base DN of the selected LDAP connection. To narrow the search, fill in User Base DN Attribute or Group Base DN Attribute in the object class definitions. |
LDAP synchronization synchronizes not only users but also the organization tree the users belong to. The parent of an organization comes from the directory tree: an OU or group that sits under another OU or group in its DN is placed under it. Active Directory cannot place a group under another group, so a group that is a member of another group is placed under that group. An organization has one parent: when a group is a member of several groups, the group with the longest DN becomes its parent. The OIDC (Keycloak) source can also synchronize the group tree in addition to users when Synchronize Groups is enabled. The Database and API sources build an organization tree too — derived from the organization path on each user row and, optionally, completed from a separate organization list query/endpoint — but none of the four sources synchronizes roles except OIDC, and Database/API do not synchronize groups as separate objects the way LDAP and OIDC do. On the organization screens, the code of an organization that comes from LDAP is shown as <provider name>: <group DN>. The stored code does not change; hover over the code to see the full value.
Database Provider
| Field | Description |
|---|---|
| User List Query | A parameterless SELECT statement that returns the users to synchronize. The returned columns must use the following aliases: username (required), email, full_name. Example: select login as username, mail as email, display_name as full_name from users |
API Authentication Provider
| Field | Description |
|---|---|
| User List URL | The endpoint that returns the list of users to synchronize. |
| HTTP Method | The HTTP method used when calling the user list endpoint. |
| Timeout | The request is terminated after this many seconds. |
| Request Body | An optional request body sent to the user list endpoint (for POST/PUT). |
| Request Headers | Optional HTTP headers sent with the user list request (e.g. Authorization). |
| Users Array JSON Path | The JSON Path expression to the user array in the response. Default: $ |
| Username JSON Path | The JSON Path expression to the username within a user item. Required. |
| Email JSON Path | The JSON Path expression to the email within a user item. |
| Full Name JSON Path | The JSON Path expression to the full name within a user item. |
OIDC Provider (Keycloak)
Available only when the provider's Vendor is Keycloak — see OIDC Provider for the full field reference.
| Field | Description |
|---|---|
| Realm | The Keycloak realm to synchronize from. |
| Admin API Base URL | The base URL of the Keycloak Admin REST API. |
| Sync Client ID | The service-account client used to call the Admin API. Requires, at minimum, the view-users and query-groups realm-management roles — see Keycloak Service Account Roles. |
| Sync Page Size | The page size used when paging through Keycloak's user list. Default: 250. |
| Synchronize Groups | When enabled, the realm's group tree is also synchronized as Credential organizations. |
| Role Sync Source | Realm Roles, Client Roles, or Groups — which Keycloak role assignment becomes the synchronized user's roles. |
Unlike the other three sources, users synchronized from Keycloak are passwordless — the password stays with Keycloak, and authentication is verified through the OIDC Authentication policy's token validation, not through a stored password. See Password Model and Username Contract.
Synchronization Scope
By default a source synchronizes everything it can read. The Synchronization Scope card on the same tab narrows that set, with separate include/exclude pattern lists for Users, Groups / Organizations (LDAP, OIDC, and Database — not yet offered on the API provider's screen), and Roles (OIDC only).
| Field | Description |
|---|---|
| Pattern Type | Glob — * matches any number of characters (the / separator included), ? matches exactly one, and every other character is literal, so a . or a ( in a pattern means that character. Regular Expression — the pattern is compiled exactly as written; see the warning below. |
| Matched Field | Which field of the record the patterns are applied to. Users can be matched on username, e-mail or full name on every source, on external identity on LDAP and OIDC, and on their DN (path) on LDAP. Groups are matched on path or name, roles on name. |
| Case Sensitivity | Off by default: patterns match regardless of case. |
| Include Patterns | While empty, every record is a candidate. Once at least one pattern is present, a record has to match one of them to stay in scope. |
| Exclude Patterns | A record matching one of these is dropped even when an include pattern also matches it — exclude always wins. |
A pattern matches the whole value, not a part of it: admin matches the user named admin and nothing else, while *admin* matches every username that contains it.
When the matched field is Path, both the record's value and the pattern are normalized before they are compared, so an LDAP DN and a Keycloak group path reduce to the same shape: CN=Dev,OU=Eng,DC=acme,DC=corp and /Eng/Dev both become /Eng/Dev. One pattern therefore means the same thing on every source, and you may write it in whichever dialect you find natural — ou=eng,dc=acme and /Eng/* are equally valid.
Filter patterns are compiled once, at the very start of the run. A pattern that does not compile, that is longer than 512 characters, or that is rejected as unsafe to evaluate (a shape that can make matching take exponentially long on non-matching input) ends the run as Failed — the run does not carry on as though the filter were absent. Continuing without it would bring back into scope everyone an exclude pattern was meant to drop, which is by far the more dangerous of the two outcomes. Globs are checked the same way, because a pattern such as *a*a*a*a*b is just as expensive once translated.
What Happens to a Record That Leaves the Scope
A user that still exists in the source but no longer matches the filter is not treated as removed from the source. Its credential is stamped as seen by the run — so the reconciliation step does not count it as stale — and the Scope Exit Policy decides what happens to it:
| Policy | Effect |
|---|---|
| Leave as is (default) | The credential is left untouched. |
| Disable | The credential is disabled; the record is kept. |
| Tombstone | The credential is marked as tombstoned. |
A manually created credential, and a credential owned by another identity source, are never touched by this policy — the same protections that apply to synchronization in general apply here.
How many records the filter dropped in a run is shown in the Filtered column of the run history. No per-user issue record is written for them: a scope narrowing that drops tens of thousands of users would otherwise fill the run's issue list and push the real skip reasons out of it.
Reconciliation Safety Net
The fields below sit under the scope filter and govern what reconciliation is allowed to do.
| Field | Description |
|---|---|
| Scope Exit Policy | What happens to a record that leaves the scope — see above. Default: Leave as is. |
| Deactivation Threshold (%) | When the records that would be deactivated exceed this share of the records the source manages, reconciliation does not run and waits for approval — see Deactivation Threshold and Reconcile Approval. Default: 20%. |
| Deactivation Threshold (records) | The same check by absolute count. Default 0, which leaves only the percentage check in effect. |
| Do not deactivate or delete when the source returns no stable external identity | Off by default. See the warning below. |
Reconciliation decides that a user is gone by not seeing it in the run. Without a stable external identity — an id that stays the same when the user is renamed — the only key is the username, so a user renamed in the source looks removed, and a destructive reconciliation acts on that appearance.
Which sources return such an identity today:
| Source | Stable external identity |
|---|---|
| OIDC (Keycloak) | Yes — the user's Keycloak UUID. |
| LDAP / Active Directory | Only when a stable attribute (for example entryUUID or objectGUID) is among the attributes fetched for the user object class. Synchronization does not request one on its own — changing the existing directory queries would be a change of behavior in itself. |
| Database | Optional — via the external_id column, when the user list query includes it. A row that leaves it blank falls back to matching by user name for that row. |
| API | Optional — via the Stable Id JSON Path field, when configured. A row it does not match falls back to matching by user name for that row. |
Current behavior is unchanged in this version. A source with no stable identity still disables or deletes exactly as it did before; the run only records a warning in its issue list so that the risk is visible where it happens. Turning Do not deactivate or delete when the source returns no stable external identity on makes reconciliation stop for that source instead of acting on a username.
That opt-in is planned to become the default in a future major version. Turning it on early is the safe direction for LDAP connections that do not fetch a stable attribute, and for Database and API sources that leave the stable-identity column or field unconfigured — the sources where a stable identity cannot be obtained at all.
Synchronization Status and History
The screen opened from Identity > Providers > Synchronization lists LDAP, Database, API, and OIDC sources in separate tabs. Each row shows:

| Field | Description |
|---|---|
| Name | The name of the identity provider, with its description under it. A source whose synchronization is off shows a Sync disabled badge. |
| Schedule | The job status as a badge (Scheduled, Running, Paused, Blocked, Complete, or Error) and the cron expression under it. A source with synchronization on but no cron expression shows a Manual only badge. |
| Last Synchronized | The completion time of the last run. |
| Result | The result of the last run as a badge: Succeeded, Warning, or Failed. Hover over the badge to see the reason. While a run of the source is queued or running, this cell shows the running badge instead. |
| Upserted, Deactivated, Skipped, Errors | The record counts processed on the last run. A zero is shown in grey. A non-zero Deactivated count is an orange badge. A non-zero Errors count is a red badge. |
Starting a Synchronization
Each row has a Synchronize… button. It starts a synchronization without waiting for the schedule. The start has two steps: a dialog where you select the scope, and a second confirmation.
Synchronize Now starts the run in the background and returns at once — the counts shown the moment the button is pressed still belong to the previous run.
The run record does not wait, though: a row appears in the Run History table the moment the button is pressed, in Queued state; it becomes Running once a node picks it up, and takes its result when it finishes. A run that never manages to start is therefore still visible on screen.
Seeing the result no longer requires a manual refresh on this screen: the history table follows the running synchronization live, and the moment the run finishes, the summary counts on the source rows above are refreshed too. The message shown after the trigger says as much — it no longer asks you to refresh the table.
The Synchronize… button is disabled for a source whose synchronization is off (the Enable Synchronization field is off). It is also disabled while a run of that source is queued or running. Hover over the button to see the reason.
A Full or Users only run can deactivate consumers. If you are not sure about the result, first run a dry run on the edit screen of the provider: Directory Preview > Full scan (dry run) for LDAP, Source Preview with Dry run for Database and API, Preview Directory for OIDC.
-
Click Synchronize… on the row of the source. The synchronization dialog opens.
-
Check the connection block at the top of the dialog. It shows the name and the description of the source, and where the source is read from:
- LDAP: the connection, the server address, the security (LDAPS or StartTLS), the base DN, the bind DN, the user base DN, the group base DN, and the membership mode.
- Database: the connection, the JDBC URL, and the database user.
- API: the user list URL, the HTTP method, and the organization list URL (when it is set).
- OIDC: the issuer URL, the realm, the admin API base URL, the client ID, and the group synchronization setting.
The block never shows a password or a secret. If you cannot view connections, some lines are missing and the dialog tells you so.
-
Select the scope under What should be synchronized?. LDAP and OIDC sources have three scopes:
- Full (Recommended): users, groups/organizations and their parents are read. Then the records that left the source are handled.
- Users only: consumers are created or updated. The organization tree does not change.
- Groups only: only the organization tree is updated. Consumers do not change.
Database and API sources support only full synchronization. For these sources, the dialog shows a note instead of the scopes.
-
Read the Last run and Expected duration lines. The expected duration comes from an earlier run of the source, preferably a run with the same scope. If there is no earlier run, the expected duration is unknown.
-
Read the information note. It changes with the scope:
- Full and Users only: at the end of the run, consumers that are no longer in the source are deactivated or deleted, according to the deactivation mode of the source.
- Groups only: at the end of the run, organizations that are no longer in the source are handled. Consumers do not change.
-
Click Start synchronization. A second confirmation opens. It shows the scope, the source, what the run changes, and the expected duration.
-
Click Yes, start to start the run. To go back to the dialog without starting a run, click No, go back.
While a run is queued or running, the Result cell of its row shows a badge such as Running · Users only or Queued · Full. The line under the badge shows the phase (Reading the source or Applying the changes) and the number of users processed so far. The page refreshes this information itself; the phase and the count change about every 15 seconds. When the run ends, the row shows the new result.
The rows learn about a running synchronization from the first page of the Synchronization History table below. If you move that table to a later page, the running badge is not shown until you go back to the first page.
A Synchronize Now button is also on the detail screen of a synchronized credential or organization, for users with identity management permission. It starts a full synchronization of the entire source provider of the record, not only that one record.
Choosing What a Manual Run Covers
On LDAP and OIDC sources the Synchronize Now button carries a scope menu; pressing the button itself still runs a full synchronization.
| Scope | What it does |
|---|---|
| Full | Users, the group/organization tree and roles, then reconciliation. This is what a scheduled run always does, and what Synchronize Now did before this version. |
| Users only | Reads users only. The group/organization tree is not read — so it is not reconciled either, and users keep the organization and roles they already have instead of being cleared by a pass that fetched nothing. A user disabled in the source is still reflected as disabled. |
| Groups only | Reads the group/organization tree only. No user is read or written; only the organization reconciliation runs. |
Database and API sources always fetch users and organizations together in a single pass, so they have no separate users-only or groups-only phase, and their button carries no scope menu — every run on these two sources is Full. A run's scope is shown in the Scope column of the run history; rows recorded before this version show Full, which is what those runs were.
The distinction the run scope carries is between "it was fetched and came back empty" and "it was never fetched". A users-only run does not read the group tree, so it makes no statement about organizations and roles, and the engine leaves both alone. A full run that reads the tree and finds a user in no group does make that statement, and the user's organization is cleared — as it always has been.
Run History
Beyond the last-run snapshot in the table above, Apinizer keeps a permanent record of every synchronization run. A Synchronization History card appears at the bottom of two screens:
- The Identity > Providers > Synchronization screen described above — the card here lists runs from every source in the current project, with a Source column identifying which provider each row belongs to.
- Each provider's own view screen (LDAP Active Directory, Database Provider, API Authentication Provider, OIDC Provider) — the same card, scoped to that source only, without the Source column.
Both surfaces show the same columns, paged from the server (10 / 20 / 50 rows per page):
| Column | Description |
|---|---|
| Source | The provider's name and type. Shown only on the project-wide Synchronization screen. |
| Started | The time the run was queued — the row is born at this moment. |
| Duration | How long the run actually executed; time spent waiting in the queue is not counted. |
| Trigger | A Scheduled or Manual badge. For a manual run, the username of the administrator who started it is shown under the badge. |
| Scope | The scope of the run as a badge: Full, Users only, Groups only, or Reconcile apply — see Choosing What a Manual Run Covers. Rows recorded before this version show Full. |
| Result | The run's current state as a badge: Queued, Running, Success, Warning, Failed, Interrupted, Cancelled, or Awaiting Reconcile Approval — see Run Lifecycle. |
| Upserted, Deactivated, Skipped, Unchanged, Errors | The record counts processed by this run — see Skipping Unchanged Users for what Unchanged means. A zero is shown in grey. A non-zero Deactivated count is an orange badge. A non-zero Errors count is a red badge. |
| Filtered | How many records the synchronization scope filter dropped. Runs recorded before the filter existed show - rather than 0: not knowing and dropping nothing are different statements. |
| Issues | The number of user-level records (warnings and errors) of the run, as a badge such as 3 issues. Click the badge to open the issues panel — see Run Issue List. Runs that executed before the issue log existed show - in this column. |
| Reason | A translated explanation of the result. A Failed, Interrupted, or Cancelled run also shows a reason badge classifying why — see Failure Reason. |
| Actions | Carries Approve Reconciliation while a staged reconciliation is still approvable — see Deactivation Threshold and Reconcile Approval. Carries Cancel while the run is still Queued or Running — see Cancelling a Run. |