PLANS / sync-organization-team-memberships-via-org-api

Plans

Sync organization team memberships with the Organization API

Set which linked teams one or more users belong to with POST https://api.cursor.com/organizations/team-memberships/sync. You send a batch of users; each row returns its own success or error. Official reference: Organization API → Sync Organization Team Memberships.

Authenticate with an Organization API key (Basic auth, key as username, empty password). This write route needs members:* (or admin:*). Enterprise only. Resolve numeric userId values and linked teamIds first with List organization members.

Request body

Field Rules
organizationId Required. Public org id (for example org_abc123). Must match the key’s organization
users Required. Non-empty array, at most 500 entries per request

Each entry needs a userId plus exactly one of:

Field Behavior
teamIds Complete set of linked team ids the user should belong to after the call. Adds missing teams and removes any linked team not listed. At most 100 teams per entry
destinationTeamId Places the user on a single team and removes every other linked team. Same effect as teamIds: [NNN]

userId accepts an integer (12345) or a string id ("user_abc123").

Move to one team

curl -X POST https://api.cursor.com/organizations/team-memberships/sync \
  -u YOUR_ORGANIZATION_API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "org_abc123",
    "users": [
      { "userId": 12345, "destinationTeamId": 8 }
    ]
  }'

Keep current teams while adding another

List every team that should remain, including the current ones:

curl -X POST https://api.cursor.com/organizations/team-memberships/sync \
  -u YOUR_ORGANIZATION_API_KEY: \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "org_abc123",
    "users": [
      { "userId": 12345, "teamIds": [7, 8] },
      { "userId": "user_abc123", "destinationTeamId": 8 }
    ]
  }'

Success response (HTTP 200)

{
  "results": [
    {
      "userId": 12345,
      "teamIds": [7, 8],
      "status": "success"
    },
    {
      "userId": "user_abc123",
      "teamIds": [8],
      "destinationTeamId": 8,
      "status": "success"
    }
  ],
  "successCount": 2,
  "errorCount": 0
}

One failed row does not roll back the others. Read every results[].status and errorMessage. Split work above 500 users into separate requests.

Common errors

Status / shape When
401 Invalid Organization API Key Wrong or missing key
401 missing members:* Key valid but lacks members write (or admin:*)
403 Not authorized Body organizationId does not match the key’s organization
404 Organization not found ({"error":"Organization not found"})
400 Missing body, empty users, more than 500 entries, or other invalid body
Per-row status: "error" (still HTTP 200) Invalid ids, user not in the org, team not linked, or user not found

Per-row examples include Invalid userId, Invalid destinationTeamId, Team is not linked to this organization, User is not a member of this organization, and User not found.

Pitfalls

  • teamIds is a full replace. Omitting a current team removes the user from it.
  • Every team in the entry must already be linked to the organization.
  • The target user must already be an organization member before sync succeeds.
  • Team Admin CSV-style moves and this endpoint share the same bulk idea; keep using the Organization key and /organizations/… path here.