Create new Atlassian Team Step
Description
Overview
A new Team approval step lets Approval Path resolve approvers directly from Atlassian Team structure, so approval definitions stay aligned with the teams an organization already maintains. It is available in both Jira and Confluence, and works for Team, Group, or any custom entity type the customer defines.
Background: why the solution works this way
A fully native Team step is not currently possible. Reading Atlassian Team membership is only available through an access method that requires an app to store an Atlassian user API token. The Atlassian Marketplace Security Enforcement Policy explicitly prohibits Marketplace apps from collecting, transmitting, or storing such tokens, and the compliant alternative (OAuth) does not yet expose the required Team scopes to Marketplace apps. A native Team step would therefore fail Atlassian security review.
To deliver the capability without breaking these rules, the design keeps all token handling on the customer side. Approval Path connects to a small service running in the customer's own environment, and that service is responsible for reading Team data.
How it works
-
The customer runs a lightweight service in their environment that reads Atlassian Team data and returns it in an agreed format.
-
When configuring the step, an administrator points Approval Path at that service, either by selecting a preconfigured source (Template mode) or by entering the service address directly (Manual mode).
-
At approval time, Approval Path asks the service to expand the selected team into its current members, who become the approvers.
-
When a person votes, Approval Path checks live whether they are still a member of the team. Someone added to the team after the approval was created can vote; someone removed is blocked immediately.
Apart from where the member list comes from, the step behaves exactly like the existing Group step: voting thresholds, add-comment-on-action, exclusions (creator, users from member picker, users from custom fields), skip-if-empty, watcher notifications, and conditions.
Responsibilities
On the customer side:
-
Host the service in their own environment.
-
Provide the service with access to the Atlassian Team data.
-
Verify the signed requests coming from Approval Path (a reference verifier is provided).
-
Allow the service address in the app configuration.
On the Approval Path side:
-
Connect to the configured service, load teams, resolve members, and run live membership checks.
-
Sign every outbound request so the customer's service can confirm it genuinely comes from Approval Path.
-
Handle voting, exclusions, thresholds, notifications, and conditions.
Security and compliance
-
The Atlassian API token used to read Team data stays entirely within the customer's service. Approval Path never collects, transmits, or stores it.
-
The integration is read-only: only team names and member account IDs needed to resolve approvers are read.
-
Communication is encrypted (HTTPS only) and authenticated. Every request is cryptographically signed on the Approval Path side and verifiable on the customer side, which prevents any other party from impersonating the integration.
-
The design is aligned with Atlassian Marketplace security requirements.
Reliability
Approval Path periodically checks that the customer's service is reachable and responding correctly. If the service becomes unavailable or rejects requests, a clear message is shown on the step and the administrator who configured the source is notified. Problems reaching the service at approval time are surfaced without silently aborting the approval process.
Getting started
To reduce setup effort, a ready-to-use reference implementation of the service is published on a public repository, in Java and in Node.js, together with documentation. The customer can deploy it as provided or use it as a starting point. The request-verification specification and a reference verifier are supplied alongside it.
Technical specification
1. Purpose
A new Team approval step: a connector that resolves approvers from sets of Atlassian users provided by an external, customer-hosted data source. Available in both Jira and Confluence. Its variants are Team, Group, and any customer-defined type; they differ only by the type value, the structure is shared.
The app does not collect, transmit, or store Atlassian API tokens. Data comes from the customer's source over a signed channel. The integration is read-only.
2. Source configuration
A global administrator defines, in Global Settings, a list of source configurations. Each configuration contains two required fields:
-
baseUrl: the base URL of the customer-hosted service.
-
type: a string identifying the entity type (for example
team,group, or any customer-defined value). This value is sent as thetypeparameter in all entity-related API requests.
After entering the baseUrl and type, the application validates the service by calling:
GET {baseUrl}/v1/health
The configuration is considered valid only if the endpoint responds with HTTP 200 and returns the entered type. If the health check fails or returns any other status, the field displays a validation error indicating that the service is unavailable.
In the step definition, the user can choose between two modes:
-
Template (default): select one of the predefined source configurations created by the global administrator.
-
Manual: specify the baseUrl and type directly within the step. Both fields are required and validated using the same health-check mechanism as in Global Settings.
3. Step form
The Team step uses a fixed icon matching the Atlassian Team icon.
The form contains a Mode selector with two values:
-
Template: select one of the source configurations defined by the global administrator. The selected template provides the baseUrl and type values.
-
Manual: enter the baseUrl and type values directly. Both fields are required and validated using the same health check as in Global Settings.
After the baseUrl and type values are resolved, the application loads the complete entity list from the configured service. Picker search is performed on our side after loading the full entity list.
The rest of the form mirrors the Group step: action type, required pass/fail votes, add-comment-on-action, exclude-from-entity (creator, users picker from the entity's members, users from custom fields), skip-if-empty (evaluated after exclusions), work-item watcher notifications, create-step condition, label condition, New badge.
4. HTTP contract
Common to all endpoints:
-
Authorization: JWT <token>(section 5), HTTPS only. -
Content-Type: application/json; charset=utf-8, path prefix/v1. -
typepassed explicitly on every entity-related request. -
Listing endpoints return all data at once (no pagination, no cursor).
Endpoint 1 - list entities (picker)
GET {baseUrl}/v1/entities?type={type}
example: GET {baseUrl}/v1/entities?type=team{"type": "team","items": [{ "id": "team-8c6b31a6", "name": "Platform" },{ "id": "team-1f2e9a44", "name": "Payments" }]}
Endpoint 2 - entity members (max limit of members 500)
GET {baseUrl}/v1/entities/{entityId}/members?type={type}
{"type": "team","entity": { "id": "team-8c6b31a6", "name": "Platform" },"members": [{ "accountId": "5b10ac8d82e05b22cc7d4ef5" },{ "accountId": "60f1c2d3e4b5a60071a2b3c4" }]}
Endpoint 3 - member-of (live per-user gate)
GET {baseUrl}/v1/entities/{entityId}/members/{accountId}?type={type}
{"type": "team","entity": { "id": "team-8c6b31a6" },"accountId": "5b10ac8d82e05b22cc7d4ef5","member": true}
Non-membership: member: false with HTTP 200 (not an error).
Endpoint 4 - health (service status)
GET {baseUrl}/v1/health
{ "status": "ok", "types": ["team"] }
Requires the same JWT as the other endpoints. It simultaneously confirms service availability, TLS correctness, and that the client correctly verifies our signature and aud. The types field declares the supported entity types (drives the type selector in the form). Healthy = HTTP 200 + status: "ok" within the timeout.
Fields
-
type: entity kind:team,group, or any customer-defined value. -
items[].id/entity.id: stable entity identifier in the customer's source; used asentityId. -
items[].name/entity.name: display name shown in the picker and errors. -
members[].accountId/accountId: Atlassian account ID. The only field required to resolve approvers. -
member:true/falsemembership gate result. -
status/types: service status and list of supported entity types.
Error format (all endpoints)
{ "error": { "code": "ENTITY_NOT_FOUND", "message": "Team team-8c6b31a6 not found or no longer visible" } }
Codes and statuses: ENTITY_NOT_FOUND (404), ENTITY_NOT_VISIBLE (403), UNAUTHORIZED (401, rejected JWT), UPSTREAM_ERROR (502). All mapped to the instance-error mechanism.
5. Request authentication
Direction: our backend signs, the customer service verifies.
-
Asymmetric algorithm (RSA/EC). A single application key; the private key stays on our backend. The public key is exposed as a JWKS at one URL, with
kidfor rotation. -
JWT claims:
header.kid - signing key identifier (key selection from JWKS, rotation)iss - application pluginKeyaud - Atlassian base URL from atlassian_host (host binding)iatexp - iat + 120 sjti - random nonce
-
Token minted per request. Client-side clock-skew leeway <= 30 s.
-
Mandatory client-side verification (documented and enforced in the reference verifier):
-
verify the signature with our public key from JWKS (selected by
kid); -
aud== the client's own Atlassian base URL, without this check host binding does not hold and cross-tenant access becomes possible; -
exp/iatwithin the validity window; optionally single-use viajti.
-
-
We provide the customer with the verification spec and a reference verifier.
6. Outbound connection security
Enforced at the HTTP-client level, independent of the allowlist (the allowlist author is the customer admin):
-
https://only; -
DNS resolution and IP validation immediately before connecting: block loopback / private / link-local / cloud-metadata ranges (DNS-rebinding protection);
-
no following redirects to internal addresses;
-
hard timeout; a defensive response-size guard (anti-DoS);
-
strict format validation:
accountIdcorrectness, ignore unknown fields (forward-compatible), handle malformed input.
7. Runtime behavior
-
At execution time the entity is expanded into its current members (Endpoint 2) and becomes the approver set; the set is refreshed on step re-evaluation.
-
Authorization and vote casting:
member-of(Endpoint 3) is called live, so a user added to the entity after the approval was created can vote, and a removed user is blocked immediately. Exclusions, already-voted status, and vote thresholds are decided locally on our side. -
Voting, exclusions, skip-if-empty (after exclusions), watcher notifications, and conditions behave identically to the Group step.
8. Monitoring, errors, and notifications
-
Health check (Endpoint 4) is called on
baseUrl/step configuration and periodically. -
On the step: on failure, a service-problem message is shown, distinguishing "service unavailable" from "service rejects our tokens, check verification on your side" (401).
-
Instance-error: health-check failures and real-call failures at execution time (missing entity, lost visibility, empty membership, unavailability, format error) are surfaced through the standard instance-error mechanism without aborting path execution. Timeout/retry on calls.
-
Global-admin notification: on health-check failure (especially periodic and at configuration time) the global admin who configured the source is notified, as the integration owner, through the existing notification mechanism.
9. Compliance and privacy
-
The app does not store Atlassian API tokens; the data source is customer-hosted, the channel is signed.
-
Read-only integration; documentation states this explicitly.
-
accountIdand membership are resolved at execution time and persisted only to the necessary extent. -
The data flow (outbound calls, JWKS endpoint,
accountIdand membership) is described in the security self-assessment and the listing's data-handling.
10. Reference implementation (sample service)
Prepare a sample implementation of a customer-hosted service that serves as a reference for customers integrating with the Team step. The service fetches Atlassian Team data using a user's API token and exposes it through the REST API in the agreed format (Endpoints 1 to 4 from section 4).
Implementations
-
Primary: Java (Spring Boot).
-
Alternative: Node.js / TypeScript, a lightweight variant covering non-Java customers.
Both implementations expose the same contract and behavior; they differ only in language and framework.
Functional scope
-
Maps Atlassian Team data onto the entity/member contract: entity list (Endpoint 1), entity members (Endpoint 2, max 500), live
member-ofgate (Endpoint 3), andhealthwith the declaredtypes(Endpoint 4). -
Fetches Team data from Atlassian using the user's API token.
-
Error responses follow the error format and codes from section 4 (
ENTITY_NOT_FOUND,ENTITY_NOT_VISIBLE,UNAUTHORIZED,UPSTREAM_ERROR).
Inbound request verification (section 5)
-
Verifies the JWT signature against our public key from JWKS (selected by
kid). -
Checks that
audequals the client's own Atlassian base URL. -
Checks that
exp/iatfall within the validity window; optionally enforces single use viajti.
Secure storage of the user's API token
-
The token is never hardcoded or committed to the repository.
-
It is loaded from environment variables or a secret manager (for example Vault, AWS Secrets Manager, GCP Secret Manager), shown as configurable options rather than a mandated provider.
-
If the service persists the token, it is stored encrypted at rest.
-
The token is kept in memory only for the duration needed to perform the call and is never written to logs or error messages.
-
The token lives exclusively within the customer-hosted service. Our app remains read-only and never collects, transmits, or stores Atlassian API tokens (consistent with sections 1 and 9).
Delivery
-
Both sample services are published to our publicly accessible Bitbucket repository.
-
They are linked from the product documentation as reference implementations for customers, alongside the verification spec and reference verifier from section 5.
Endpoint overview
-
1
GET /v1/entities?type=- list entities (picker) -
2
GET /v1/entities/{entityId}/members?type=- entity members -
3
GET /v1/entities/{entityId}/members/{accountId}?type=- live is-member gate -
4
GET /v1/health- service status (+ supportedtypesdeclaration)