Developer docs

API for developers

One REST API per team: read members, trainings and evaluations, manage assignments and cases, read the catalog and team analytics your team has made available — and get a webhook when a training finishes or an evaluation is scored.

This page gets you to a first successful call in about five minutes. The full reference lists every operation with its parameters, an example response and the errors it can return; the machine-readable form is at openapi.v1.json (OpenAPI 3.1, generated from the same code as these pages).

Requirements

The interface is part of the Team and Enterprise plans. A team admin or owner creates the first key under Team → Integrations & API in the app; that needs the team right schnittstellen (the owner has it by default and can grant it to others).

Quickstart

1

Create a key

In the app under Team → Integrations & API → API keys. Pick the "Read only" template for your first try — the secret is shown exactly once, at creation.

2

Make your first call

GET /me needs no scope and confirms the key works: your tenant, its scopes and the team's privacy switches.

curl https://app.sip.coach/api/public/v1/me \
  -H "Authorization: Bearer sipk_live_ab12cd34_…"
import httpx

response = httpx.get(
    "https://app.sip.coach/api/public/v1/me",
    headers={"Authorization": "Bearer sipk_live_ab12cd34_…"},
)
print(response.json())
const res = await fetch("https://app.sip.coach/api/public/v1/me", {
  headers: { Authorization: "Bearer sipk_live_ab12cd34_…" },
});
console.log(await res.json());
<?php
$context = stream_context_create(["http" => ["header" =>
    "Authorization: Bearer sipk_live_ab12cd34_…"]]);
$response = file_get_contents("https://app.sip.coach/api/public/v1/me", false, $context);
echo $response;

Response:

{
  "tenant_id": "6a9c3e3e-9b8e-4b7b-9e9d-2b7b1a2c3d4e",
  "kind": "team",
  "scopes": [
    "users:read",
    "trainings:read",
    "evaluations:read"
  ],
  "rate_limit_per_min": 600,
  "privacy": {
    "member_detail_enabled": true
  }
}
3

Keep going

With a key using the "Read and assignments" template, GET /users lists members and GET /trainings their trainings — every other operation is in the reference.

Authentication

Every request carries the key in Authorization: Bearer sipk_live_<prefix>_<secret>. The key belongs to the team, not a person; its scopes are fixed when it is created (widening them means creating a new key). Revocation takes effect immediately; rotation lets the old and new key overlap for a chosen grace period.

ScopeAllows
users:read / users:writeRead members / invite, change role, remove
trainings:readTraining metadata
evaluations:readEvaluations (score, dimensions, tips — no transcript)
assignments:read / assignments:writeAssignments
cases:read / cases:writeTeam cases
catalog:readCatalog within the team's effective policy
analytics:readTeam analytics (aggregated)
webhooks:manageWebhook endpoints

The app also offers the templates "Read only" (every :read scope) and "Read and assignments" (adds assignments:write) when creating a key, or a custom selection from the table above.

Tenancy and rights

The tenant is fixed the moment the key is created — no call names a team, in the path or the body. A foreign id returns 404, exactly like one that never existed: from the outside, the two are indistinguishable.

Errors

Every error follows RFC 9457 (application/problem+json). code is stable and never localized; title follows Accept-Language (de or en).

{
  "type": "https://sip.coach/entwickler/api/fehler#insufficient_scope",
  "title": "Insufficient scope",
  "status": 403,
  "code": "insufficient_scope",
  "detail": "scope 'assignments:write' required",
  "request_id": "a7e8b2c1-4d3e-4f5a-9b6c-7d8e9f0a1b2c",
  "required_scope": "assignments:write"
}
StatusCodeWhen
400bad_requestA dependency rejected the request without its own code; the status follows that dependency.
401unauthorizedKey missing, unknown, malformed, expired, revoked, or the caller's IP is not allowed — one code for every reason.
403insufficient_scopeA required scope is missing; detail names it.
403privacy_restrictedThe team has turned off per-person detail (member_detail_enabled).
403api_disabledTenant suspended or ended.
403vorgabe_gesperrtA policy assigned to the recipient blocks this situation, personality or industry (assignments only).
404not_foundEither never existed, or belongs to a different tenant — indistinguishable from the outside.
409conflictThe email already belongs to a team member or the team's owner, or a concurrent call with the same Idempotency-Key is still in progress.
409seat_limit_reachedAll of the team's seats are taken.
409last_adminThe member is the team's only active admin; changing their role or removing them would leave the team without one.
409ownerThe team's owner cannot be removed or have their role changed through this API.
422validation_errorA query, path or body value failed validation. A form error detected while parsing the request carries errors (a list); a business-rule error the application checks instead carries field (a single field name).
422idempotency_mismatchThe Idempotency-Key header was reused with a different request body.
422kombination_ungueltigThe situation, personality or industry id is unknown, not visible to this team, or violates a catalog rule (assignments only).
429rate_limitedLimit reached; Retry-After names the number of seconds to wait.
500internalUnexpected error; the body deliberately reveals no internals — request_id helps when contacting support.
503unavailableThe customer API is temporarily unavailable.

Every code with an explanation: Error codes.

Rate limits and idempotency

120 requests per minute per key, plus a per-tenant limit your team can configure (default 600/minute) — whichever is tighter applies. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset.

POST calls accept an Idempotency-Key header (up to 100 characters, valid 24 hours): the same key with the same body returns the first response again without repeating the action; the same key with a different body returns 422 idempotency_mismatch.

Pagination

List routes (e.g. GET /users, GET /assignments) accept ?limit= (default 50, maximum 200) and ?cursor=, and respond with a data field (the list) and next_cursor. next_cursor is an opaque value — do not interpret it as a page number or offset, just pass it back unchanged on the next call; null means there is no further page.

Versioning

Path version v1. Within v1, only additive changes (new fields, new routes, new webhook event types) — ignore unknown response fields. A breaking change ships as v2 alongside v1 for at least twelve months, announced via Deprecation/Sunset response headers and an email to every admin with an active key.

Privacy

The team's privacy switches apply to the API exactly as they do in the app. With member_detail_enabled off, routes with per-person data return only aggregates or 403 privacy_restricted, and webhook bodies carry neither a score nor a user_id.

CRM connections

A connected CRM system is set up in the app, under Team → CRM connections — not through this API. Once a connection is active, GET /crm/deals returns the synchronized deals and GET /crm/objections the team's objection catalog; both routes are read-only, paginated like any other list route.

Where to go next