Stitchflow
X Ads logo

X Ads User Management API Guide

API workflow

How to automate user lifecycle operations through APIs with caveats that matter in production.

UpdatedMar 18, 2026

Summary and recommendation

The X Ads API exposes a dedicated account_users sub-resource under each ads account for programmatic user provisioning and deprovisioning.

All write operations require OAuth 1.0a with user-context;

OAuth 2.0 Bearer Token (app-only) is explicitly unsupported for account_users mutations.

Ads API access is not automatically available - it requires explicit approval from X before any integration work can begin.

Integrating X Ads into an identity graph requires resolving Twitter user_ids via the Twitter API v2 user lookup endpoint before any provisioning call, since the Ads API operates on numeric user_ids, not usernames or email addresses.

API quick reference

Has user APIYes
Auth methodOAuth 1.0a (user-context required for Ads API calls; OAuth 2.0 Bearer Token is not supported for Ads API write operations)
Base URLOfficial docs
SCIM availableNo
SCIM plan requiredN/A

Authentication

Auth method: OAuth 1.0a (user-context required for Ads API calls; OAuth 2.0 Bearer Token is not supported for Ads API write operations)

Setup steps

  1. Apply for Ads API access at developer.twitter.com and receive approval from the X Ads API team.
  2. Create a Project and App in the X Developer Portal to obtain a Consumer Key and Consumer Secret.
  3. Implement the OAuth 1.0a three-legged flow to obtain an Access Token and Access Token Secret on behalf of the ads account owner.
  4. Include OAuth 1.0a Authorization header (with oauth_consumer_key, oauth_token, oauth_signature, etc.) on every API request.
  5. Ensure the authenticated user has the appropriate role on the target Ads Account (e.g., ACCOUNT_MANAGER or higher).

Required scopes

Scope Description Required for
ads:read Read access to ads account data including users, campaigns, and analytics. GET operations on account users and account metadata
ads:write Write access to ads account data including creating and modifying account users. POST/PUT/DELETE operations on account users

User object / data model

Field Type Description On create On update Notes
id string Unique identifier for the account user association. system-generated immutable Ads API user ID, distinct from the Twitter user ID.
user_id string The Twitter user ID of the user being granted access. required immutable Must correspond to an existing Twitter account.
account_id string The Ads account ID this user association belongs to. required immutable Ads account IDs are base-36 encoded strings.
permission_level enum The role/permission level of the user on the account. required updatable Values: ACCOUNT_MANAGER, CAMPAIGN_ANALYST, ORGANIC_ANALYST, CREATIVE_MANAGER, DSO_ADVERTISER.
scope enum Scope of access: ACCOUNT (full account) or CAMPAIGN (specific campaigns). required updatable CAMPAIGN scope requires specifying campaign_ids.
campaign_ids array[string] List of campaign IDs the user has access to when scope is CAMPAIGN. conditional updatable Only applicable when scope=CAMPAIGN.
created_at datetime Timestamp when the user association was created. system-generated immutable ISO 8601 format.
updated_at datetime Timestamp when the user association was last updated. system-generated system-generated ISO 8601 format.
deleted boolean Whether the user association has been deleted/revoked. false set to true on DELETE Soft-delete pattern; deleted records may still appear in responses with deleted=true.

Core endpoints

List account users

  • Method: GET
  • URL: https://ads-api.twitter.com/12/accounts/{account_id}/account_users
  • Watch out for: Returns both active and soft-deleted users unless filtered. Use with_deleted=false to exclude deleted associations.

Request example

GET /12/accounts/abc123/account_users
Authorization: OAuth oauth_consumer_key="...", ...

Response example

{
  "data": [{"id":"xyz","user_id":"123","permission_level":"ACCOUNT_MANAGER","scope":"ACCOUNT","deleted":false}],
  "total_count": 1
}

Get a single account user

  • Method: GET
  • URL: https://ads-api.twitter.com/12/accounts/{account_id}/account_users/{account_user_id}
  • Watch out for: The account_user_id is the Ads API association ID, not the Twitter user_id.

Request example

GET /12/accounts/abc123/account_users/xyz
Authorization: OAuth oauth_consumer_key="...", ...

Response example

{
  "data": {"id":"xyz","user_id":"123","permission_level":"CAMPAIGN_ANALYST","scope":"ACCOUNT","deleted":false}
}

Add user to account

  • Method: POST
  • URL: https://ads-api.twitter.com/12/accounts/{account_id}/account_users
  • Watch out for: The user_id must be a valid Twitter account. The authenticating user must have ACCOUNT_MANAGER permission or higher on the target account.

Request example

POST /12/accounts/abc123/account_users
Content-Type: application/x-www-form-urlencoded

user_id=456&permission_level=CAMPAIGN_ANALYST&scope=ACCOUNT

Response example

{
  "data": {"id":"new_id","user_id":"456","permission_level":"CAMPAIGN_ANALYST","scope":"ACCOUNT","deleted":false}
}

Update account user permissions

  • Method: PUT
  • URL: https://ads-api.twitter.com/12/accounts/{account_id}/account_users/{account_user_id}
  • Watch out for: Full replacement semantics on PUT; omitting optional fields may reset them to defaults.

Request example

PUT /12/accounts/abc123/account_users/xyz
Content-Type: application/x-www-form-urlencoded

permission_level=ACCOUNT_MANAGER&scope=ACCOUNT

Response example

{
  "data": {"id":"xyz","user_id":"456","permission_level":"ACCOUNT_MANAGER","scope":"ACCOUNT","deleted":false}
}

Remove user from account

  • Method: DELETE
  • URL: https://ads-api.twitter.com/12/accounts/{account_id}/account_users/{account_user_id}
  • Watch out for: Soft-delete only; the record remains retrievable with deleted=true. The user loses access immediately upon deletion.

Request example

DELETE /12/accounts/abc123/account_users/xyz
Authorization: OAuth oauth_consumer_key="...", ...

Response example

{
  "data": {"id":"xyz","deleted":true}
}

Get authenticated user's account access

  • Method: GET
  • URL: https://ads-api.twitter.com/12/accounts/{account_id}
  • Watch out for: Returns account metadata, not user list. Use /account_users sub-resource for user enumeration.

Request example

GET /12/accounts/abc123
Authorization: OAuth oauth_consumer_key="...", ...

Response example

{
  "data": {"id":"abc123","name":"My Ads Account","timezone":"America/New_York","deleted":false}
}

Rate limits, pagination, and events

  • Rate limits: The Ads API enforces per-endpoint rate limits. Limits vary by endpoint and are documented per resource. Requests exceeding limits receive HTTP 429 responses.
  • Rate-limit headers: Yes
  • Retry-After header: No
  • Rate-limit notes: The Ads API returns rate limit information in response headers: x-rate-limit, x-rate-limit-remaining, and x-rate-limit-reset. Specific per-endpoint limits are listed in the official API reference. The docs do not explicitly document a Retry-After header.
  • Pagination method: cursor
  • Default page size: 200
  • Max page size: 1000
  • Pagination pointer: cursor
Plan Limit Concurrent
Standard Ads API Access Varies by endpoint; commonly 100–1000 requests per 15-minute window per endpoint 0
  • Webhooks available: No
  • Webhook notes: The X Ads API does not offer webhooks for user-management events such as user addition, removal, or permission changes. The X Account Activity API (for organic Twitter activity) is separate and does not cover Ads account user events.
  • Alternative event strategy: Poll the GET /accounts/{account_id}/account_users endpoint periodically to detect changes in account user membership and permissions.

SCIM API status

  • SCIM available: No
  • SCIM version: Not documented
  • Plan required: N/A
  • Endpoint: Not documented

Limitations:

  • X Ads does not provide a SCIM 2.0 endpoint.
  • No native IdP (Okta, Entra ID, Google Workspace, OneLogin) provisioning connectors are available for X Ads.
  • User provisioning must be performed directly via the Ads API account_users endpoints.

Common scenarios

Three core automation scenarios are well-supported by the API.

First, onboarding: resolve the target user's Twitter user_id via GET /2/users/by/username/:username, then POST to /12/accounts/{account_id}/account_users with permission_level and scope;

confirm deleted=false in the response.

Second, access auditing: GET /12/accounts/{account_id}/account_users?with_deleted=false and paginate via the cursor parameter (default page size 200, max 1000) until next_cursor is null, logging user_id, permission_level, scope, and campaign_ids per record.

Third, permission downgrade: retrieve the account_user_id via GET, then PUT the full user object with the revised permission_level PUT uses full-replacement semantics, so omitting any field may reset it to a default.

No webhooks exist for user-management events;

change detection requires polling.

Onboard a new team member to an Ads account

  1. Obtain the new user's Twitter user_id (via Twitter API v2 GET /2/users/by/username/:username).
  2. Authenticate via OAuth 1.0a as an existing ACCOUNT_MANAGER on the target ads account.
  3. POST to /12/accounts/{account_id}/account_users with user_id, permission_level, and scope.
  4. Confirm the response returns deleted=false and the expected permission_level.

Watch out for: The new user must have an existing Twitter account. You cannot create a Twitter account via the Ads API.

Audit all users on an Ads account

  1. Authenticate via OAuth 1.0a as an ACCOUNT_MANAGER.
  2. GET /12/accounts/{account_id}/account_users?with_deleted=false to retrieve active users.
  3. Paginate using the cursor parameter until next_cursor is null.
  4. For each user record, log user_id, permission_level, scope, and campaign_ids.

Watch out for: Default responses may include soft-deleted users; always pass with_deleted=false for active-only audits.

Downgrade a user's permission level

  1. GET /12/accounts/{account_id}/account_users to find the account_user_id for the target user.
  2. PUT /12/accounts/{account_id}/account_users/{account_user_id} with the new permission_level (e.g., CAMPAIGN_ANALYST) and existing scope.
  3. Verify the response reflects the updated permission_level.

Watch out for: PUT is a full replacement; include all required fields (scope, permission_level) in the request body to avoid unintended resets.

Why building this yourself is a trap

Several non-obvious constraints will surface during implementation. The API uses soft-deletes: removed account_user records remain in GET responses unless with_deleted=false is explicitly passed, which will silently inflate active-user counts in any identity graph sync that omits this filter. Account IDs are base-36 encoded strings, not integers - URL construction must handle this correctly.

The permission_level enum values are Ads API-specific and do not map directly to the UI role labels, requiring an explicit translation layer in any provisioning pipeline. CAMPAIGN-scoped access requires campaign_ids to be passed explicitly; omitting them when scope=CAMPAIGN returns an error.

API versioning is path-based (currently /12/); X periodically deprecates older versions, so version pinning without a changelog monitor is a maintenance risk. Finally, rate limits vary per endpoint and must be read from per-endpoint documentation - the x-rate-limit, x-rate-limit-remaining, and x-rate-limit-reset headers are present in responses, but no Retry-After header is documented.

Automate X Ads 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.

Every app coverage, including apps without APIs
60+ app integrations plus browser automation for apps without APIs
IT graph reconciliation across apps and your IdP
Less than a week to launch, maintained as APIs and admin consoles change
SOC 2 Type II. ~2 hours of your team's time

UpdatedMar 18, 2026

* Details sourced from official product documentation and admin references.

Keep exploring

Related apps

Abnormal Security logo

Abnormal Security

API Only
AutomationAPI only
Last updatedMar 2026

Abnormal Security is an enterprise email security platform focused on detecting and investigating threats such as phishing, account takeover (ATO), and vendor email compromise. It does not support SCIM provisioning, which means every app in your stack

ActiveCampaign logo

ActiveCampaign

API Only
AutomationAPI only
Last updatedFeb 2026

ActiveCampaign uses a group-based permission model: every user belongs to exactly one group, and all feature-area access (Contacts, Campaigns, Automations, Deals, Reports, Templates) is configured at the group level, not per individual. The default Adm

ADP logo

ADP

API Only
AutomationAPI only
Last updatedFeb 2026

ADP Workforce Now is a mid-market to enterprise HCM platform that serves as the HR source of record for employee data — payroll, benefits, time, and talent. User access is governed by a hybrid permission model: predefined security roles (Security Maste