Skip to main content
This guide is for integrators calling legacy internal Teams endpoints (/teams) who are moving to the new Teams API (/external/v1/teams).

New to the Teams API?

API Overview

What the Teams API returns and how it’s structured.

API Scopes

Scopes required to access each Teams API endpoint.

OAuth 2.0 Guide

End-to-end OAuth 2.0 workflow guide for Pleo integrations.

OAuth 2.0 with Postman

How to configure Postman to authenticate using OAuth 2.0 and call the Pleo API.

Standalone API Key Guide

End-to-end guide for authenticating with standalone API keys.

Standalone API Keys with Postman

How to make API calls using standalone API keys in Postman.

Key Differences

  • Authentication moves from a logged-in user session (JWT) to OAuth 2.0 client credentials.
  • Authorisation moves from a company OWNER role check on the caller to a token-scoped permission check (team:read / team:write / team:delete) against the team’s company or organisation.
  • List responses use cursor-based pagination instead of offset-based pagination.
  • Single-resource responses are wrapped in a data envelope.
  • The reviewers field has been removed from the team response.
  • On create, companyId is derived from the access token rather than the request body, giving the same caller experience as before, just with a different token type. A team can also be created directly under an organizationId, which takes precedence over companyId if both are provided. See Creating a Team.
  • Team name uniqueness is now validated on create. See Behaviour Change: Team Names Must Be Unique.
  • Updating a team moves from PUT (all fields required) to PATCH (all fields optional). See Breaking Change: PUT → PATCH, All Fields Now Optional.
  • The sentinel for deleting a team’s code changes from null to an empty string "". See Breaking Change: Deleting the Code.
  • Adding and removing team members moves to dedicated /members endpoints and no longer accepts multiple employee IDs via PUT. See Managing Team Members.

Endpoint Mapping

The following sections cover each change in detail.

Authentication and Permission Scopes

Before: the legacy endpoints authenticated the caller as a logged-in Pleo user and required the OWNER role on the target company. After: the new API authenticates callers as OAuth 2.0 clients. Access is granted per-scope, and the token must carry access to the company or organisation the team belongs to: There is no longer a concept of a per-user role (e.g. OWNER) on the request. Access is entirely determined by what the client’s token is scoped to.

Creating a Team

POST /external/v1/teams creates a new team for a company or an organisation the OAuth 2.0 token has access to.

companyId and organizationId Are Optional Body Fields

Before: the legacy endpoint extracted companyId from the caller’s JWT server-side (via preload: jwtCompanyId), so it was never part of the request body:
After: the new endpoint accepts optional organizationId and companyId fields in the body:
  • If organizationId is supplied, it takes precedence: the team is created directly under that organisation, and the token must be scoped to it.
  • Otherwise, companyId is used if supplied (the token must have access to that company), and defaults to the company the token is scoped to if omitted.
For the common case of a company-scoped token creating a team for its own company, the request is unchanged from before: companyId can still be omitted:
An organisation-scoped token can create a team for one of its companies by supplying that company’s companyId explicitly, or create an organisation-level team by supplying organizationId instead:

Behaviour Change: Team Names Must Be Unique

Unlike the legacy endpoint, which did not validate team name uniqueness, the new endpoint enforces the same validation as the rest of Teams API: a team name must be unique (case-insensitively) within the company and within its organisation. Creating a team with a name that already exists in the company or organisation now returns a 400 Bad Request, where it would previously have succeeded. If your integration relies on being able to create teams with duplicate names, you’ll need to adjust for this before migrating.

Updating a Team

PATCH /external/v1/teams/{teamId} partially updates a team’s name and/or code. It requires a company- or organisation-scoped access token authorised for the team, and the team:write scope, the same scope used by Create Team.

Breaking Change: PUT → PATCH, All Fields Now Optional

Before: the legacy endpoint is a PUT and, per standard PUT semantics, expects the full resource on every call: both name and code are required fields:
After: the new endpoint is a PATCH. Both fields are optional, and any field omitted from the request body is left unchanged:

Breaking Change: Deleting the Code (null vs Empty String)

Before: send code: null to delete a team’s code:
After: send code: "" (empty string) to delete it. Sending null, or omitting code entirely, leaves the existing code unchanged:

Managing Team Members

Adding and removing team members moves from a single PUT/DELETE pair on /teams/:teamId/employees/:employeeId to two dedicated endpoints under /members, documented in OpenAPI schema as Add Team Members and Remove Team Member.

Adding Team Members

POST /external/v1/teams/{teamId}/members adds one or more Employees to a Team. It requires the same team:write scope as Create Team and Update Team.
The response is 200 OK with a data array containing one result per requested Employee, rather than failing the whole call if one Employee can’t be added:

Removing a Team Member

DELETE /external/v1/teams/{teamId}/members/{employeeId} removes a single Employee from a Team. It requires the team:write scope. If the Employee is not currently a member of the Team, this is a no-op rather than an error: the call still returns 200 OK, with false as the response body (true if an Employee was actually removed).

Response Structure Changes

Envelope

GET /external/v1/teams/{teamId} and PATCH /external/v1/teams/{teamId} now wrap the team in a data object, rather than returning the team as the top-level response body:
GET /external/v1/teams returns a cursor-paginated response, replacing the previous offset-based pagination (limit/offset query params) used on GET /teams.

reviewers Field Removed

The reviewers field (the list of employee IDs assigned as a team’s reviewers) has been removed from the team response entirely. It is no longer present on any new Teams API response:
If your integration reads reviewer assignments from the Teams API response, that data is no longer available on this resource and you’ll need to source it elsewhere.

Errors

No Impact on Existing Integrations

This change doesn’t affect any webhooks or events your integration relies on. Team creation behaves the same whether you use the legacy or new endpoint: no new event types, and no changes to existing event payloads.

Team Update Events

Updating a team now publishes two separate update events instead of one, compared to the legacy endpoint.
  • Legacy: a single update event fires whenever the team’s name or code changes, carrying both fields.
  • New: two independent events fire instead: one when name changes, one when code changes. Updating both fields in a single request triggers both events. This isn’t new behaviour introduced by this migration: the new endpoint has always emitted these two separate events.