Integrations
Server-to-server access for external systems that want to pull defect state and assignees. Distinct from PATs — PATs belong to a person; an integration belongs to a system.
When to use this
Use the integration API when an external system (a PMO tool, an executive dashboard, a Slack bot, a data warehouse ETL) needs to read defects on a schedule. The contract is intentionally narrow — read-only, defect-shaped, and stable — so you can wire it once and forget it.
Use a Personal Access Token (PAT) instead when a person (or their CI runner acting on their behalf) is the actor — they sign in, they own results, their permissions apply.
The two big problems this solves
1. Identity mismatch
The integrator’s system has its own project IDs, application IDs, module IDs, and user IDs that don’t match ours. Multiple integrators may be on the same TCM tenant, each with their own naming. A single global “external_id” column on each entity wouldn’t work — two integrators would fight over it.
We solve this with a per-integration mapping table:
{
"tenant_id": "...",
"integration_id": "...",
"kind": "project" | "application" | "module" | "user",
"internal_id": "uuid (ours)",
"external_id": "string (theirs)"
}Two integrations on the same tenant can both map our project abc-123 to different external IDs without conflict (PROJ-42 for one, P_007 for the other).
2. Auth that’s not tied to a human
Integration tokens are bearer credentials with the prefix tcm_int_. Unlike PATs, they’re bound to an integration row — not a user — so there’s no risk of the human’s account getting disabled and the integration silently breaking. Tokens carry explicit scopes (see below); the integration has no implicit role.
Scopes
Each token is minted with one or more scopes. A request to a scope-gated route without the right scope returns 403 integration.scope_missing.
| Scope | What it lets the token do |
|---|---|
defects:read | Read defects + assignees via GET /integrations/defects |
mappings:read | Inspect the integrator’s own mapping table |
mappings:write | Upsert and delete mappings |
Lifecycle
- Tenant admin creates an integration in the TCM web UI under Admin → Integrations.
- Admin mints a token for the integration with the required scopes. The plaintext is shown exactly once — they copy it into the integrator’s secret store.
- Admin pushes the ID mappings the integration will use — pick each of our projects / applications / modules / users from a dropdown and paste the integrator’s external ID. Same can be done by the integrator’s bootstrap script via the
mappings:writescope. - The integrator’s system calls
GET /integrations/defectson a schedule, using its own external IDs as filters. Useupdated_sincefor incremental sync.
Token format
Every integration token looks like tcm_int_<random>. The tcm_int_ prefix lets our auth middleware tell integration tokens apart from PATs and JWTs without parsing them. The random portion is 32 base64url-encoded bytes (~256 bits of entropy). Only the SHA-256 hash is stored — the plaintext is shown only at mint time and never recoverable from the database.
Rate limits
Integration requests share the per-tenant rate limit (300 req/min) with the rest of the tenant’s traffic. If one integration is noisy, the admin can revoke its token or split it into a second integration to spread the limit.
Known gap: assignee-only changes don’t bump updated_at
Defect updated_at is bumped on any UPDATE to the defects row, but adding/removing an assignee touches a separate join table. That means a pure-updated_since incremental sync will miss assignment changes. Workaround: schedule a full resync nightly. A trigger that bumps the parent on assignee changes is on the roadmap.
What’s next
- Reference — endpoint shapes, query params, response examples.