Summary and recommendation
The Admin SDK Directory API (`https://admin.googleapis.com/admin/directory/v1`) provides full CRUD over Google Cloud Identity users and groups via OAuth 2.0. Service accounts must be granted domain-wide delegation in the Admin console - OAuth consent alone is insufficient. The caller must impersonate a super-admin email as the subject; regular user tokens cannot manage other users.
All user operations share a default quota of 2,400 QPM per project; rate limit errors return HTTP 403 (not 429) with `reason: rateLimitExceeded` in the error body, so check the error payload rather than the status code alone.
Key field caveats: use the immutable `id` field (not `primaryEmail`) as the stable reference for updates, since email addresses can change. PATCH on array fields (`emails`, `phones`, `organizations`) replaces the entire array - send the full desired state, not a delta.
New accounts may take up to 24 hours to propagate across all Google services despite the API returning 200 immediately.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 (service account with domain-wide delegation, or user OAuth flow) |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Cloud Identity Premium ($7.20/user/month) |
Authentication
Auth method: OAuth 2.0 (service account with domain-wide delegation, or user OAuth flow)
Setup steps
- Create a Google Cloud project in the Google Cloud Console (console.cloud.google.com).
- Enable the Admin SDK API for the project under APIs & Services > Library.
- Create a Service Account under IAM & Admin > Service Accounts; download the JSON key.
- In the Google Admin console (admin.google.com), go to Security > API Controls > Domain-wide Delegation.
- Add the service account client ID and grant the required OAuth 2.0 scopes.
- In code, use the service account credentials with subject impersonation set to a super-admin email to call the Directory API.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| https://www.googleapis.com/auth/admin.directory.user | Full read/write access to user accounts in the directory | Create, update, delete users |
| https://www.googleapis.com/auth/admin.directory.user.readonly | Read-only access to user accounts | List and get users |
| https://www.googleapis.com/auth/admin.directory.group | Full read/write access to groups | Create, update, delete groups and memberships |
| https://www.googleapis.com/auth/admin.directory.group.readonly | Read-only access to groups | List and get groups |
| https://www.googleapis.com/auth/admin.directory.userschema | Read/write access to custom user schema definitions | Managing custom user attributes |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| primaryEmail | string | User's primary email address; serves as the unique identifier | required | optional | Must be within a verified domain of the organization |
| id | string | Immutable unique ID assigned by Google | server-assigned | read-only | Use this as the stable identifier; primaryEmail can change |
| name.givenName | string | User's first name | required | optional | Max 60 characters |
| name.familyName | string | User's last name | required | optional | Max 60 characters |
| name.fullName | string | User's full name (read-only, derived from givenName + familyName) | server-assigned | read-only | |
| password | string | User's password (write-only) | required | optional | Min 8 characters. Can be provided as plain text or hashed (specify hashFunction) |
| suspended | boolean | Whether the user account is suspended | optional | optional | Suspended users cannot sign in but retain their data |
| isAdmin | boolean | Whether the user has super-admin privileges | optional | optional | Use makeAdmin endpoint to change; setting via update may not work directly |
| orgUnitPath | string | Organizational unit path the user belongs to | optional | optional | Defaults to root OU if not specified |
| emails | array | List of email addresses for the user | optional | optional | Includes aliases; primaryEmail is always included |
| phones | array | List of phone numbers | optional | optional | Each entry has 'value' and 'type' fields |
| addresses | array | List of physical addresses | optional | optional | |
| organizations | array | List of organizations the user belongs to (department, title, etc.) | optional | optional | Contains fields like department, title, costCenter |
| externalIds | array | External identifiers (e.g., employee ID) | optional | optional | Each entry has 'value' and 'type' (e.g., 'organization') |
| customSchemas | object | Custom attributes defined via schema API | optional | optional | Schema must be created first via the Schemas API |
| changePasswordAtNextLogin | boolean | Forces password change on next login | optional | optional | |
| agreedToTerms | boolean | Whether user has agreed to Terms of Service | read-only | read-only | |
| lastLoginTime | string (datetime) | Timestamp of last login | server-assigned | read-only | ISO 8601 format |
| creationTime | string (datetime) | Timestamp when account was created | server-assigned | read-only | ISO 8601 format |
| recoveryEmail | string | Recovery email address for the user | optional | optional |
Core endpoints
List Users
- Method: GET
- URL:
https://admin.googleapis.com/admin/directory/v1/users?customer=my_customer&maxResults=100&pageToken={token} - Watch out for: Use customer=my_customer to scope to your own domain. Without domain or customer param, the request fails. nextPageToken must be passed as pageToken in subsequent requests.
Request example
GET /admin/directory/v1/users?customer=my_customer&maxResults=100
Authorization: Bearer {access_token}
Response example
{
"kind": "admin#directory#users",
"nextPageToken": "abc123",
"users": [
{"id": "1234", "primaryEmail": "user@example.com",
"name": {"fullName": "Jane Doe"}}
]
}
Get User
- Method: GET
- URL:
https://admin.googleapis.com/admin/directory/v1/users/{userKey} - Watch out for: userKey can be the user's primary email, alias email, or immutable Google ID. Using the immutable ID is safest for updates.
Request example
GET /admin/directory/v1/users/user@example.com
Authorization: Bearer {access_token}
Response example
{
"id": "1234567890",
"primaryEmail": "user@example.com",
"name": {"givenName": "Jane", "familyName": "Doe"},
"suspended": false,
"orgUnitPath": "/Engineering"
}
Create User
- Method: POST
- URL:
https://admin.googleapis.com/admin/directory/v1/users - Watch out for: Password is required. Domain must be verified and owned by the organization. New users may take up to 24 hours to propagate across all Google services.
Request example
POST /admin/directory/v1/users
Content-Type: application/json
{
"primaryEmail": "newuser@example.com",
"name": {"givenName": "New", "familyName": "User"},
"password": "SecurePass123!"
}
Response example
{
"id": "9876543210",
"primaryEmail": "newuser@example.com",
"name": {"fullName": "New User"},
"creationTime": "2024-01-15T10:00:00.000Z"
}
Update User (partial)
- Method: PATCH
- URL:
https://admin.googleapis.com/admin/directory/v1/users/{userKey} - Watch out for: PATCH is preferred over PUT for partial updates. PUT replaces the entire user object and requires all required fields.
Request example
PATCH /admin/directory/v1/users/user@example.com
Content-Type: application/json
{
"name": {"givenName": "Updated"},
"orgUnitPath": "/Sales"
}
Response example
{
"id": "1234567890",
"primaryEmail": "user@example.com",
"name": {"givenName": "Updated", "familyName": "Doe"},
"orgUnitPath": "/Sales"
}
Delete User
- Method: DELETE
- URL:
https://admin.googleapis.com/admin/directory/v1/users/{userKey} - Watch out for: Deleted users are moved to a 20-day recovery window before permanent deletion. Data can be transferred before deletion using the Data Transfer API.
Request example
DELETE /admin/directory/v1/users/user@example.com
Authorization: Bearer {access_token}
Response example
HTTP 204 No Content
Suspend User
- Method: PATCH
- URL:
https://admin.googleapis.com/admin/directory/v1/users/{userKey} - Watch out for: Suspended users immediately lose access to Google services but their account and data are retained. Licenses continue to be consumed.
Request example
PATCH /admin/directory/v1/users/user@example.com
Content-Type: application/json
{"suspended": true}
Response example
{
"id": "1234567890",
"primaryEmail": "user@example.com",
"suspended": true
}
Make User Admin
- Method: POST
- URL:
https://admin.googleapis.com/admin/directory/v1/users/{userKey}/makeAdmin - Watch out for: This is a dedicated endpoint for toggling super-admin status. Setting isAdmin via PATCH on the user object does not reliably change admin status.
Request example
POST /admin/directory/v1/users/user@example.com/makeAdmin
Content-Type: application/json
{"status": true}
Response example
HTTP 204 No Content
List Group Members
- Method: GET
- URL:
https://admin.googleapis.com/admin/directory/v1/groups/{groupKey}/members - Watch out for: Requires admin.directory.group.readonly scope at minimum. Nested group membership is not expanded by default; use includeDerivedMembership=true query param.
Request example
GET /admin/directory/v1/groups/eng-team@example.com/members
Authorization: Bearer {access_token}
Response example
{
"kind": "admin#directory#members",
"members": [
{"email": "user@example.com", "role": "MEMBER", "type": "USER"}
]
}
Rate limits, pagination, and events
- Rate limits: Admin SDK Directory API enforces per-project and per-user quotas. Default quota is 2,400 queries per minute (QPM) per project for most user operations. Quota increases can be requested via Google Cloud Console.
- Rate-limit headers: No
- Retry-After header: No
- Rate-limit notes: HTTP 429 or 403 with reason 'rateLimitExceeded' or 'userRateLimitExceeded' is returned when quota is exceeded. Implement exponential backoff. Quota increases are requestable through the Google Cloud Console quota page.
- Pagination method: token
- Default page size: 100
- Max page size: 500
- Pagination pointer: pageToken
| Plan | Limit | Concurrent |
|---|---|---|
| Default (all plans) | 2,400 QPM per project (read/write combined for users.list, users.get, users.insert, etc.) | 0 |
- Webhooks available: Yes
- Webhook notes: Google Admin SDK supports push notifications (webhooks) via the Reports API and Directory API watch endpoints. Clients register a HTTPS receiver URL to receive change notifications for directory resources.
- Alternative event strategy: Google Cloud Pub/Sub can be used as an alternative to direct HTTPS webhooks for more reliable event delivery. Cloud Audit Logs also captures admin activity and can be exported to Pub/Sub or BigQuery.
- Webhook events: users (directory watch), groups (directory watch), admin activity events (Reports API), login events (Reports API), token events (Reports API)
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Cloud Identity Premium ($7.20/user/month)
Endpoint: Configured per third-party IdP (e.g., Okta, Entra ID) pointing to Google Cloud Identity as the SCIM target. Google Cloud Identity acts as the SCIM service provider; the IdP is the SCIM client.
Supported operations: Create user (POST /Users), Update user (PATCH /Users/{id}), Deactivate/suspend user (PATCH active=false), Delete user (DELETE /Users/{id}), List users (GET /Users), Get user (GET /Users/{id}), Create group (POST /Groups), Update group membership (PATCH /Groups/{id}), Delete group (DELETE /Groups/{id})
Limitations:
- SCIM provisioning requires Cloud Identity Premium; not available on Cloud Identity Free tier
- SSO must be configured as a prerequisite for SCIM provisioning
- Google Cloud Identity acts as SCIM service provider only; it does not push SCIM to external systems natively
- Custom SCIM attribute mappings are limited to supported directory attributes
- SCIM endpoint URL and bearer token are generated per IdP integration in the Admin console
Common scenarios
Three integration patterns cover the majority of production use cases:
Provision and assign to OU: POST to /users with primaryEmail, name, password, and orgUnitPath. Store the returned immutable id. If orgUnitPath is omitted, the user lands in the root OU; the API does not create OUs automatically. Follow with a group membership POST if group assignment is required.
Offboard (suspend then delete): PATCH {"suspended": true} to revoke access immediately while preserving data and the license seat. Use the Data Transfer API before deletion if Drive or Calendar data must be retained. DELETE the account when ready; a 20-day recovery window applies, after which deletion is permanent. Suspended users continue consuming a license until the account is deleted.
SCIM sync from an external IdP: Requires Cloud Identity Premium and a configured SSO integration as prerequisites. Generate the SCIM endpoint URL and bearer token from the Admin console, then configure the IdP (Okta, Entra ID) with those credentials. The bearer token does not expire on a fixed schedule but must be rotated periodically - regenerating it in the Admin console immediately invalidates the previous token, breaking provisioning until the IdP configuration is updated.
Provision a new employee and assign to an OU
- Authenticate using a service account with domain-wide delegation and scope https://www.googleapis.com/auth/admin.directory.user.
- POST to /admin/directory/v1/users with primaryEmail, name.givenName, name.familyName, password, and orgUnitPath set to the target OU (e.g., '/Engineering').
- Store the returned immutable 'id' field as the stable reference for future updates.
- Optionally POST to /admin/directory/v1/groups/{groupKey}/members to add the user to relevant groups.
Watch out for: If orgUnitPath is omitted, the user lands in the root OU. OU must already exist; the API does not create OUs automatically.
Offboard a user (suspend then delete)
- PATCH /admin/directory/v1/users/{userKey} with {"suspended": true} to immediately revoke access while preserving data.
- Optionally use the Data Transfer API to transfer Drive/Calendar data to a manager before deletion.
- After the offboarding window, DELETE /admin/directory/v1/users/{userKey} to remove the account.
- Account enters a 20-day recovery window; use POST /admin/directory/v1/users/{userKey}/undelete within that window if needed.
Watch out for: Suspended users still consume a license. Delete the account to free the license. Deletion is irreversible after the 20-day recovery window.
Sync users from an external IdP via SCIM
- Ensure Cloud Identity Premium is active and SSO is configured for the target IdP (Okta or Entra ID).
- In the Google Admin console, navigate to Security > Authentication > SSO with third-party IdP and enable SCIM provisioning.
- Copy the generated SCIM endpoint URL and bearer token from the Admin console.
- In the IdP (e.g., Okta), configure the Google Cloud Identity SCIM app with the endpoint URL and bearer token.
- Map IdP user attributes to Google directory attributes (e.g., userName → primaryEmail, givenName, familyName).
- Enable provisioning in the IdP and run an initial sync; monitor for errors in both the IdP provisioning logs and Google Admin audit logs.
Watch out for: SCIM bearer tokens generated by Google Admin console do not expire on a fixed schedule but should be rotated periodically. Regenerating the token in Admin console immediately invalidates the old token, breaking provisioning until the IdP is updated.
Why building this yourself is a trap
The Directory API manages directory identity (who exists, group membership, OU placement) but does not manage Google Cloud IAM bindings (what resources a user can access).
An integration that only calls the Directory API will leave IAM policy bindings orphaned on deletion and will not grant or revoke project-level access on provisioning - those operations require separate calls to the Cloud Resource Manager and IAM APIs.
Teams building an identity graph across Google Cloud must account for both layers: directory state via the Directory API and authorization state via the IAM API, with the resource hierarchy (Organization → Folder → Project → Resource) determining where bindings are inherited.
Stitchflow's MCP server with 60+ deep IT/identity integrations handles this cross-API identity graph reconciliation, including IAM binding cleanup on offboarding, without requiring custom orchestration between the two API surfaces.
Automate Google Cloud workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.