Summary and recommendation
Keap exposes a REST API at `https://api.infusionsoft.com/crm/rest/v1` (legacy domain; intentional) with OAuth 2.0 authentication. User management operations - list, get, create, update, and deactivate - are available under the `/users` resource. There is no SCIM 2.0 endpoint and no SAML support; any IdP integration requires a custom REST API layer.
The only available OAuth scope is `full`, which grants broad read/write access across all resources. There is no user-management-only scope, so integrations built on this API carry elevated privilege by design. Access tokens expire after 24 hours; refresh token rotation must be handled explicitly in the integration layer.
For teams building an identity graph across SaaS tools, Keap user records expose `id`, `global_user_id`, `infusionsoft_id`, `email_address`, `admin`, `partner`, and `status` fields - sufficient to anchor a cross-system identity node, though the binary `admin` boolean is the only machine-readable role signal available via the API.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 |
| Base URL | Official docs |
| SCIM available | No |
Authentication
Auth method: OAuth 2.0
Setup steps
- Register a developer application at https://keys.developer.keap.com/
- Obtain a Client ID and Client Secret from the developer console
- Redirect the user to https://accounts.infusionsoft.com/app/oauth/authorize with client_id, redirect_uri, response_type=code, and scope parameters
- Exchange the returned authorization code for an access token via POST to https://api.infusionsoft.com/token
- Include the access token as a Bearer token in the Authorization header for all API requests
- Use the refresh token to obtain new access tokens before expiry (access tokens expire in 24 hours)
Required scopes
| Scope | Description | Required for |
|---|---|---|
| full | Full access to all API endpoints including read and write on contacts, users, and other resources | All user management operations |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Unique identifier for the user | system-assigned | read-only | Used as path parameter in user-specific endpoints |
| given_name | string | User's first name | required | optional | |
| family_name | string | User's last name | required | optional | |
| email_address | string | Primary email address of the user | required | optional | Must be unique within the account |
| global_user_id | integer | Global identifier across Keap accounts | system-assigned | read-only | |
| infusionsoft_id | string | Legacy Infusionsoft username/ID | system-assigned | read-only | Retained for backward compatibility |
| admin | boolean | Whether the user has admin privileges | optional | optional | |
| partner | boolean | Whether the user is a partner/affiliate | optional | optional | |
| status | string | Account status of the user (e.g., Active, Inactive) | system-assigned | read-only via this endpoint | Activation is handled via a separate invite/activation flow |
| time_zone | string | User's time zone setting | optional | optional | IANA time zone format |
| phone_number | string | User's phone number | optional | optional | |
| website | string | User's website URL | optional | optional | |
| job_title | string | User's job title | optional | optional | |
| address | object | User's address object (line1, line2, locality, region, zip_code, country_code) | optional | optional |
Core endpoints
List Users
- Method: GET
- URL:
https://api.infusionsoft.com/crm/rest/v1/users - Watch out for: Returns only users belonging to the authenticated app's account. Pagination uses limit/offset; max limit is 1000.
Request example
GET /crm/rest/v1/users?limit=100&offset=0
Authorization: Bearer {access_token}
Response example
{
"users": [
{"id": 1, "given_name": "Jane", "family_name": "Doe",
"email_address": "jane@example.com", "admin": true}
],
"count": 1
}
Get User
- Method: GET
- URL:
https://api.infusionsoft.com/crm/rest/v1/users/{userId} - Watch out for: Returns 404 if the userId does not belong to the authenticated account.
Request example
GET /crm/rest/v1/users/42
Authorization: Bearer {access_token}
Response example
{
"id": 42,
"given_name": "Jane",
"family_name": "Doe",
"email_address": "jane@example.com",
"admin": true,
"status": "Active"
}
Create User
- Method: POST
- URL:
https://api.infusionsoft.com/crm/rest/v1/users - Watch out for: Creating a user triggers an invitation email to the provided email address. The user status will be 'Pending' until they accept. User seat limits apply per plan.
Request example
POST /crm/rest/v1/users
Authorization: Bearer {access_token}
Content-Type: application/json
{"given_name":"John","family_name":"Smith","email_address":"john@example.com","admin":false}
Response example
{
"id": 99,
"given_name": "John",
"family_name": "Smith",
"email_address": "john@example.com",
"status": "Pending"
}
Update User
- Method: PATCH
- URL:
https://api.infusionsoft.com/crm/rest/v1/users/{userId} - Watch out for: Partial updates are supported. Email address changes may require re-verification.
Request example
PATCH /crm/rest/v1/users/99
Authorization: Bearer {access_token}
Content-Type: application/json
{"given_name":"Jonathan","admin":true}
Response example
{
"id": 99,
"given_name": "Jonathan",
"family_name": "Smith",
"email_address": "john@example.com",
"admin": true
}
Get Current Authenticated User
- Method: GET
- URL:
https://api.infusionsoft.com/crm/rest/v1/oauth/connect/userinfo - Watch out for: Returns the user associated with the current OAuth token. Useful for identifying which user authorized the app.
Request example
GET /crm/rest/v1/oauth/connect/userinfo
Authorization: Bearer {access_token}
Response example
{
"sub": "42",
"email": "jane@example.com",
"given_name": "Jane",
"family_name": "Doe"
}
List Users (v2)
- Method: GET
- URL:
https://api.infusionsoft.com/crm/rest/v2/users - Watch out for: v2 endpoints may use cursor/token-based pagination instead of offset. Check response for next_page_token. v2 is still evolving; verify endpoint availability in the official reference.
Request example
GET /crm/rest/v2/users?page_size=50
Authorization: Bearer {access_token}
Response example
{
"users": [...],
"next_page_token": "abc123"
}
Delete User (Deactivate)
- Method: DELETE
- URL:
https://api.infusionsoft.com/crm/rest/v1/users/{userId} - Watch out for: Keap may deactivate rather than hard-delete users to preserve data integrity. Verify behavior in sandbox before production use. Not all plan tiers may expose this endpoint.
Request example
DELETE /crm/rest/v1/users/99
Authorization: Bearer {access_token}
Response example
HTTP 204 No Content
Rate limits, pagination, and events
- Rate limits: Keap enforces rate limits per application. The documented limit is 25 requests per second per app. Burst limits may apply.
- Rate-limit headers: Yes
- Retry-After header: No
- Rate-limit notes: HTTP 429 is returned when the rate limit is exceeded. Official docs recommend exponential backoff. Exact header names (e.g., X-RateLimit-Remaining) are referenced in the API but full header spec is not fully documented publicly.
- Pagination method: offset
- Default page size: 1000
- Max page size: 1000
- Pagination pointer: limit / offset
| Plan | Limit | Concurrent |
|---|---|---|
| All plans | 25 requests/second per application | 0 |
- Webhooks available: Yes
- Webhook notes: Keap supports REST Hooks (webhook subscriptions) via the API. Hooks can be registered programmatically to receive event notifications via HTTP POST to a specified URL.
- Alternative event strategy: Polling the /users or /contacts endpoints is an alternative for systems that cannot receive inbound webhooks. Note: user-specific webhook events (e.g., user.add) are not explicitly documented; contact-level events are the primary webhook targets.
- Webhook events: contact.add, contact.edit, contact.delete, opportunity.add, opportunity.edit, task.add, task.edit, order.add, payment.add, invoice.paid, tag.applied, tag.removed
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: Not documented
- Endpoint: Not documented
Limitations:
- Keap does not offer a native SCIM 2.0 endpoint
- No native SAML SSO support; community requests for SAML/SCIM remain unimplemented as of early 2025
- Okta integration is available only via Secure Web Authentication (SWA), not SCIM provisioning
- User provisioning must be performed via the REST API or manually through the Keap UI
Common scenarios
Three primary automation scenarios are supported by the current API surface:
Provision a new user: POST to
/crm/rest/v1/userswithgiven_name,family_name,email_address, andadminflag. Keap automatically sends an invitation email - this cannot be suppressed via the API. User status will bePendinguntil the invite is accepted; pollGET /users/{userId}to confirm activation. If the account has reached its plan seat limit, the POST will fail; additional seats must be added through the Keap billing UI before the API call will succeed.Sync users to an external directory: Call
GET /crm/rest/v1/users?limit=1000&offset=0and paginate via offset increments. There is no delta or incremental sync endpoint; full-list polling against a locally cached snapshot is the only change-detection mechanism. Webhook events exist for contact-level changes but user-specific events (e.g.,user.add) are not explicitly documented.Update role programmatically: Send
PATCH /crm/rest/v1/users/{userId}with{"admin": true}or{"admin": false}. This is the full extent of API-accessible role control; granular permission toggles within the Standard User role are only configurable through the Keap UI.
Provision a new internal user via API
- Authenticate via OAuth 2.0 and obtain an access token with 'full' scope
- Check current user count against plan seat limit by calling GET /crm/rest/v1/users
- POST to /crm/rest/v1/users with given_name, family_name, email_address, and admin flag
- Keap sends an invitation email to the new user automatically
- Poll GET /crm/rest/v1/users/{userId} to confirm status transitions from 'Pending' to 'Active' after user accepts invite
Watch out for: If the account has reached its user seat limit, the POST will fail. Additional users cost $39/user/month and must be added via the Keap billing UI before the API call will succeed.
Sync Keap users to an external directory
- Authenticate via OAuth 2.0
- Call GET /crm/rest/v1/users with limit=1000&offset=0 to retrieve all users
- Paginate using offset increments until the returned count is less than the limit
- Map Keap user fields (id, given_name, family_name, email_address, admin, status) to the external directory schema
- Store the Keap user id as the external reference key for future updates
Watch out for: There is no delta/incremental sync endpoint; full list polling is required. For change detection, compare against a locally cached snapshot or use webhook events on contact-level changes as a proxy signal.
Update user role (admin flag) programmatically
- Authenticate via OAuth 2.0
- Retrieve the target user's id via GET /crm/rest/v1/users or by storing it at provisioning time
- Send PATCH /crm/rest/v1/users/{userId} with body {"admin": true} or {"admin": false}
- Confirm the change by calling GET /crm/rest/v1/users/{userId} and verifying the admin field
Watch out for: Keap's permission model is binary (admin vs. non-admin) at the API level; granular role-based access control is managed within the Keap UI and is not fully exposed via the REST API.
Why building this yourself is a trap
The most significant integration risk is the coexistence of v1 and v2 API endpoints with differing pagination behavior - v1 uses limit/offset, while v2 uses cursor/token-based pagination. Some resources exist only in v2, and the v2 surface is still evolving; endpoint availability should be verified against the official reference before building against it.
Rate limits are enforced at 25 requests/second per application, not per user. Shared integrations or high-frequency polling will hit this ceiling faster than single-tenant use suggests. HTTP 429 is returned on breach; exponential backoff is recommended, but the full rate-limit header spec is not completely documented publicly.
The DELETE endpoint for users (/crm/rest/v1/users/{userId}) deactivates rather than hard-deletes, preserving data integrity - but this behavior should be verified in a sandbox before production use, as availability may vary by plan tier.
Finally, because there is no SCIM layer, any identity graph built on Keap data requires the integration to own the full provisioning state machine, including pending-to-active status tracking and seat-limit pre-checks.
Automate Keap 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.