Summary and recommendation
Basecamp 3 exposes a REST API authenticated via OAuth 2.0, with no named scopes - the token inherits all permissions of the authenticated user. The `account_id` required for every endpoint must be retrieved from `https://launchpad.37signals.com/authorization.json` post-OAuth; it is not available from the token response directly.
All user list endpoints paginate at a fixed page size of 15 records using RFC 5988 Link headers (`rel='next'`, `rel='prev'`). There is no `page` query parameter. Rate limiting is enforced at 50 requests per 10-second window per access token; HTTP 429 responses include a `Retry-After` header.
A descriptive `User-Agent` header is required on every request or calls may be blocked.
For teams operating an MCP server with ~100 deep IT/identity integrations, Basecamp's REST API covers read and write operations on people and project membership, but lacks bulk operations and user lifecycle webhooks - SCIM is the preferred path for provisioning workflows at scale.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 |
| Base URL | Official docs |
| SCIM available | Yes |
| SCIM plan required | Business (Pro Unlimited, $299/month flat rate) with SSO enabled |
Authentication
Auth method: OAuth 2.0
Setup steps
- Register your application at https://launchpad.37signals.com/integrations to obtain a client_id and client_secret.
- Redirect users to https://launchpad.37signals.com/authorization/new?type=web_server&client_id={client_id}&redirect_uri={redirect_uri} to obtain an authorization code.
- Exchange the authorization code for an access token via POST to https://launchpad.37signals.com/authorization/token?type=web_server&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}&code={code}.
- Use the returned access_token as a Bearer token in the Authorization header for all API requests.
- Include a descriptive User-Agent header identifying your application and contact email as required by Basecamp.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| N/A | Basecamp 3 OAuth 2.0 does not use named scopes; access is granted to all resources the authenticated user can access. | All API operations |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | integer | Unique person identifier | system-assigned | immutable | |
| attachable_sgid | string | Signed global ID for use in rich text | system-assigned | immutable | |
| name | string | Full name of the person | required | optional | |
| email_address | string | Primary email address | required | optional | Must be unique within the account |
| personable_type | string | Type of person (User or Client) | system-assigned | immutable | |
| title | string | Job title | optional | optional | |
| bio | string | Short biography | optional | optional | |
| location | string | Location string | optional | optional | |
| created_at | datetime | ISO 8601 timestamp of account creation | system-assigned | immutable | |
| updated_at | datetime | ISO 8601 timestamp of last update | system-assigned | system-assigned | |
| admin | boolean | Whether the person is an account admin | optional | optional | |
| owner | boolean | Whether the person is the account owner | system-assigned | immutable | |
| client | boolean | Whether the person is a client user | optional | optional | |
| employee | boolean | Whether the person is an employee | optional | optional | |
| time_zone | string | IANA time zone name | optional | optional | |
| avatar_url | string | URL to the person's avatar image | system-assigned | immutable via API | |
| company | object | Associated company object with id and name | optional | optional | |
| can_manage_projects | boolean | Permission to manage projects | optional | optional | |
| can_manage_people | boolean | Permission to manage people | optional | optional |
Core endpoints
List all people on account
- Method: GET
- URL:
https://3.basecampapi.com/{account_id}/people.json - Watch out for: Returns only active (non-trashed) people. Paginated at 15 per page via Link headers.
Request example
GET /people.json
Authorization: Bearer {token}
User-Agent: MyApp (contact@example.com)
Response example
[{"id":1234,"name":"Jane Doe","email_address":"jane@example.com","admin":true,"created_at":"2023-01-10T12:00:00.000Z"}]
Get a single person
- Method: GET
- URL:
https://3.basecampapi.com/{account_id}/people/{person_id}.json - Watch out for: Returns 404 if the person is trashed or does not belong to the account.
Request example
GET /people/1234.json
Authorization: Bearer {token}
Response example
{"id":1234,"name":"Jane Doe","email_address":"jane@example.com","admin":true,"title":"Engineer"}
Get the authenticated person (me)
- Method: GET
- URL:
https://3.basecampapi.com/{account_id}/my/profile.json - Watch out for: Returns the profile of the token owner only.
Request example
GET /my/profile.json
Authorization: Bearer {token}
Response example
{"id":5678,"name":"John Smith","email_address":"john@example.com","admin":false}
List people on a project
- Method: GET
- URL:
https://3.basecampapi.com/{account_id}/projects/{project_id}/people.json - Watch out for: Only returns people currently active on the specified project.
Request example
GET /projects/9999/people.json
Authorization: Bearer {token}
Response example
[{"id":1234,"name":"Jane Doe","email_address":"jane@example.com"}]
Update project membership (grant/revoke access)
- Method: PUT
- URL:
https://3.basecampapi.com/{account_id}/projects/{project_id}/people/users.json - Watch out for: This endpoint manages project-level access, not account-level user creation. Users must already exist on the account.
Request example
PUT /projects/9999/people/users.json
Content-Type: application/json
{"grant":[1234],"revoke":[5678]}
Response example
{"granted":[{"id":1234,"name":"Jane Doe"}],"revoked":[{"id":5678,"name":"John Smith"}]}
Invite people to account (create users)
- Method: POST
- URL:
https://3.basecampapi.com/{account_id}/people/users.json - Watch out for: Sends an invitation email. The user is not active until they accept. Admin privileges required to call this endpoint.
Request example
POST /people/users.json
Content-Type: application/json
{"name":"New User","email_address":"new@example.com","title":"Designer"}
Response example
{"id":9012,"name":"New User","email_address":"new@example.com","created_at":"2024-01-01T00:00:00.000Z"}
Update a person
- Method: PUT
- URL:
https://3.basecampapi.com/{account_id}/people/{person_id}.json - Watch out for: Only account admins can update other users. Limited fields are updatable via the REST API; SCIM is preferred for provisioning workflows.
Request example
PUT /people/1234.json
Content-Type: application/json
{"title":"Senior Engineer","admin":true}
Response example
{"id":1234,"name":"Jane Doe","title":"Senior Engineer","admin":true}
Trash (deactivate) a person
- Method: DELETE
- URL:
https://3.basecampapi.com/{account_id}/people/{person_id}.json - Watch out for: This trashes (soft-deletes) the user, removing their access. It does not permanently delete the account. Requires admin privileges.
Request example
DELETE /people/1234.json
Authorization: Bearer {token}
Response example
HTTP 204 No Content
Rate limits, pagination, and events
- Rate limits: Basecamp enforces a rate limit of 50 requests per 10-second window per access token. Exceeding this returns HTTP 429.
- Rate-limit headers: Yes
- Retry-After header: Yes
- Rate-limit notes: When rate limited, Basecamp returns HTTP 429 with a Retry-After header indicating seconds to wait. The API also returns X-RateLimit-Limit and X-RateLimit-Remaining headers.
- Pagination method: token
- Default page size: 15
- Max page size: 15
- Pagination pointer: Link header (next/prev rel links); page param not directly exposed
| Plan | Limit | Concurrent |
|---|---|---|
| All plans | 50 requests per 10 seconds | 0 |
- Webhooks available: Yes
- Webhook notes: Basecamp 3 supports webhooks that can be configured per project. They fire on events related to project resources. There is no dedicated user/people lifecycle webhook (e.g., user created/deactivated).
- Alternative event strategy: Poll GET /people.json periodically to detect user changes, or use SCIM for provisioning lifecycle events via your IdP.
- Webhook events: create (messages, todos, comments, etc.), update (messages, todos, etc.), trash (resource trashed), restore (resource restored), archive (project archived)
SCIM API status
SCIM available: Yes
SCIM version: 2.0
Plan required: Business (Pro Unlimited, $299/month flat rate) with SSO enabled
Supported operations: GET /Users (list users), GET /Users/{id} (get user), POST /Users (create/provision user), PUT /Users/{id} (replace user), PATCH /Users/{id} (update user attributes), DELETE /Users/{id} (deprovision user)
Limitations:
- Requires SSO (SAML) to be configured and active before SCIM can be enabled.
- Only available on the Business/Pro Unlimited plan ($299/month).
- Group (team) provisioning via SCIM is not documented as supported; only user provisioning.
- Supported IdPs include Okta and Microsoft Entra ID (Azure AD); Google Workspace and OneLogin are not officially supported.
- SCIM token is generated within Basecamp account settings and must be stored securely.
Common scenarios
Three primary integration scenarios are supported by the API:
Provision a new employee: POST to /people/users.json to send an invitation, then poll GET /people.json until the user appears as active (invitation acceptance is required before project assignment). Then PUT to /projects/{project_id}/people/users.json with {"grant": [new_user_id]}. SCIM + IdP is the zero-touch alternative for Business plan accounts.
Deprovision a departing employee: GET /people.json to resolve the user's id by email_address, then DELETE /people/{person_id}.json to soft-trash the user. This retains all historical data. For Business plan customers, SCIM-triggered DELETE from the IdP is the recommended path and eliminates the polling step.
Sync all account users to an external directory: Paginate GET /people.json following Link: rel='next' headers until exhausted. Map id, name, email_address, admin, and created_at fields to your directory schema. A 500-person account requires approximately 34 sequential requests; implement Retry-After handling for HTTP 429 responses throughout.
Provision a new employee and add them to a project
- Authenticate via OAuth 2.0 and obtain access_token and account_id from https://launchpad.37signals.com/authorization.json.
- POST to /people/users.json with name and email_address to invite the user to the account.
- Wait for the user to accept the invitation (poll GET /people.json and check for the new user's presence and active status).
- PUT to /projects/{project_id}/people/users.json with {"grant": [new_user_id]} to add them to the relevant project.
Watch out for: The user must accept the email invitation before they appear as active and can be granted project access. Automate with SCIM + IdP for zero-touch provisioning.
Deprovision a departing employee
- Authenticate via OAuth 2.0.
- GET /people.json to find the user's id by email_address.
- DELETE /people/{person_id}.json to trash (deactivate) the user, revoking all account and project access.
- If using SCIM: send a DELETE /scim/v2/{account_id}/Users/{scim_user_id} from your IdP to automate this step.
Watch out for: DELETE via REST API is a soft trash; the user's data is retained. SCIM-based deprovisioning triggered by IdP offboarding is the recommended approach for Business plan customers.
Sync all account users to an external directory
- Authenticate via OAuth 2.0.
- GET /people.json and follow Link header pagination (rel='next') until all pages are retrieved.
- Map each person object (id, name, email_address, admin, created_at) to your directory schema.
- Store the Basecamp id for future update/delete operations.
Watch out for: Page size is fixed at 15; a 500-person account requires ~34 sequential requests. Respect the 50 req/10s rate limit and implement Retry-After handling for HTTP 429 responses.
Why building this yourself is a trap
The REST API does not support bulk user operations natively - every invite, update, or removal is a single synchronous call, and invitation-based provisioning introduces an asynchronous gap (user is inactive until they accept the email). There is no user lifecycle webhook; detecting new or removed users requires polling GET /people.json on a schedule.
SCIM 2.0 is available at https://3.basecampapi.com/scim/v2/{account_id} and supports full user provisioning lifecycle (GET, POST, PUT, PATCH, DELETE on /Users), but it is gated behind both the Pro Unlimited plan ($299/month) and an active SAML SSO configuration. SCIM cannot be enabled without SSO already in place.
Group provisioning via SCIM is not documented as supported - only user-level provisioning. Supported IdPs are limited to Okta and Microsoft Entra ID (Azure AD); Google Workspace and OneLogin have no official SCIM support path.
Automate Basecamp 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.