---
title: "LDAP directory sync"
---

# LDAP directory sync

Store an LDAP or Active Directory connection in Neural Inverse Govern, test it and sync its groups into the directory. LDAP is partial and is not a sign-in method.

## Maturity

LDAP and Active Directory support is **partial**. What works: storing one connection per organisation, testing it, and syncing the directory's groups into Govern's directory groups, by hand or on a schedule. What does not: LDAP is not a sign-in method. The connections that sign people in to Govern are SAML, OpenID Connect and social; use [SAML](https://neuralinverse.com/docs/govern/sso-saml/) or [OIDC](https://neuralinverse.com/docs/govern/sso-oidc/) for single sign-on, and [SCIM](https://neuralinverse.com/docs/govern/scim/) for provisioning.

## Configure the connection

The console page is **Identity Center > LDAP directory sync**. Through the API:

```bash
curl -sS -X PUT "$KEYSTONE_URL/api/v1/organizations/$ORG_ID/ldap" \
  -H "Authorization: Bearer $ADMIN_TOKEN" -H 'Content-Type: application/json' \
  -d '{
    "host": "ldap.corp.example.com",
    "port": 636,
    "use_tls": true,
    "bind_dn": "cn=admin,dc=example,dc=com",
    "bind_password": "secret",
    "base_dn": "dc=example,dc=com",
    "user_filter": "(uid={{.Username}})",
    "group_mappings": { "cn=admins": "admin", "cn=users": "member" }
  }'
```

- LDAP, LDAPS and StartTLS are supported.
- The bind password is encrypted with AES-256-GCM and never returned. `GET` answers `bind_password_set` instead, and an empty `bind_password` on `PUT` keeps the stored one.
- TLS certificate verification is on by default. Skipping it is refused outside development.

## Test and sync

| Route | Permission | Purpose |
|---|---|---|
| `GET /api/v1/organizations/{id}/ldap` | `ldap:read` | The connection, without its password, and `last_sync` |
| `PUT /api/v1/organizations/{id}/ldap` | `ldap:update` | Configure the connection |
| `POST /api/v1/organizations/{id}/ldap/test` | `ldap:create` | Test connectivity and the service account bind |
| `POST /api/v1/organizations/{id}/ldap/sync` | `ldap:create` | Sync the directory's groups into Govern now |

`last_sync` reports how the last manual or scheduled sync ended:

```json
{
  "last_sync": {
    "at": "2026-10-10T09:00:00Z",
    "status": "failed",
    "message": "The directory server refused the bind DN and password.",
    "last_succeeded_at": "2026-10-09T09:00:00Z",
    "groups_created": 2,
    "groups_updated": 1,
    "groups_deleted": 0,
    "members": 14
  }
}
```

`status` is `succeeded` or `failed`. `message` is Govern's own wording of a failure, never the directory server's error text. After a failure, the counts and `last_succeeded_at` are those of the last success.
