Partner-APIPartner API v1
Mit der Partner-API lesen freigegebene Software-Anbieter Daten ihrer gemeinsamen Kunden aus SolvePlus (S+): Termine, Schichten, Zeiteinträge, Rapporte, Kunden, Einsatzorte und Personal-Basisdaten. Jeder Zugriff braucht die Zustimmung des Inhabers der jeweiligen Firma.
Basis-URL: https://splus-app.de/api/partner/v1 · Format: JSON (UTF-8) · Zeiten: ISO-8601 in UTC · Fehler: application/problem+json
Die Spezifikation ist im Aufbau: Einzelne Felder können sich noch an die Implementierung angleichen. Ignorieren Sie unbekannte Felder – neue optionale Felder sind keine inkompatible Änderung.
With the Partner API, approved software vendors read data of their shared customers from SolvePlus (S+): appointments, shifts, time entries, daily reports, customers, sites and basic staff data. Every access requires the consent of the owner of the respective company.
Base URL: https://splus-app.de/api/partner/v1 · Format: JSON (UTF-8) · Times: ISO-8601 in UTC · Errors: application/problem+json
The specification is still evolving: individual fields may still be aligned with the implementation. Ignore unknown fields – new optional fields are not a breaking change. The specification texts (endpoint descriptions) are in German.
Authentifizierung: OAuth 2.0 mit PKCEAuthentication: OAuth 2.0 with PKCE
Die API nutzt den Authorization-Code-Flow mit PKCE (Methode S256 ist Pflicht). Sie brauchen eine App im Partnerportal mit Client-ID (spc_…), Client-Secret und exakt hinterlegter https-Redirect-URI.
- Verifier und Challenge erzeugen: zufälliger
code_verifier(43–128 Zeichen), darauscode_challenge = BASE64URL(SHA256(code_verifier)). Den Verifier geheim halten. - Browser des Inhabers weiterleiten auf
/oauth/autorisierenmitstate(Schutz vor CSRF – prüfen Sie ihn bei der Rückkehr). - Inhaber meldet sich an und stimmt zu. Er sieht Ihre Firma, den Zweck, die Datenarten, das Hosting-Land und die Scopes. Danach kommt der Browser mit
?code=…&state=…(oder?error=access_denied) zurück. - Code tauschen: innerhalb von 2 Minuten, einmalig, serverseitig mit Client-Secret und
code_verifier. - API aufrufen mit
Authorization: Bearer spa_…. Access-Tokens gelten 15 Minuten. - Erneuern mit dem Refresh-Token (30 Tage, rotierend). Speichern Sie jedes Mal das neue Refresh-Token.
The API uses the authorization code flow with PKCE (method S256 is mandatory). You need an app in the partner portal with client ID (spc_…), client secret and an exactly registered https redirect URI.
- Create verifier and challenge: random
code_verifier(43–128 characters), thencode_challenge = BASE64URL(SHA256(code_verifier)). Keep the verifier secret. - Redirect the owner's browser to
/oauth/autorisierenwith astate(CSRF protection – verify it on return). - The owner signs in and consents. They see your company, purpose, data categories, hosting country and scopes. The browser returns with
?code=…&state=…(or?error=access_denied). - Exchange the code: within 2 minutes, once, server-side with client secret and
code_verifier. - Call the API with
Authorization: Bearer spa_…. Access tokens are valid for 15 minutes. - Refresh with the refresh token (30 days, rotating). Always store the new refresh token.
Beispiele (curl)Examples (curl)
Scopes
Wirksam ist immer die Schnittmenge aus angefordertem Scope, Freigabe der App durch den Betreiber und Zustimmung des Inhabers. Außerdem muss das zugehörige Modul beim Mandanten gebucht sein – sonst antwortet der Endpunkt mit 403 scope_fehlt.
The effective scopes are always the intersection of the requested scope, the operator's approval of the app and the owner's consent. The corresponding module must also be booked by the tenant – otherwise the endpoint answers 403 scope_fehlt.
…
EndpunkteEndpoints
Spezifikation wird geladen …Loading specification …
Pagination
Listen-Endpunkte liefern {"daten": [...], "naechster_cursor": "…" | null}. Übergeben Sie naechster_cursor unverändert als ?cursor=, bis er null ist. limit liegt zwischen 1 und 100 (Standard 50).
Für den Abgleich nur geänderter Daten: ?aenderung_seit=2026-09-01T00:00:00Z. Merken Sie sich den größten geaendert_am-Wert der letzten Seite als Startpunkt für den nächsten Lauf. Besser noch: Webhooks nutzen und nur bei Bedarf abrufen.
List endpoints return {"daten": [...], "naechster_cursor": "…" | null}. Pass naechster_cursor unchanged as ?cursor= until it is null. limit is between 1 and 100 (default 50).
To sync only changed data: ?aenderung_seit=2026-09-01T00:00:00Z. Remember the highest geaendert_am of the last page as the starting point for the next run. Even better: use webhooks and fetch only when needed.
Rate-Limits und KontingenteRate limits and quotas
- Jede Antwort enthält
X-RateLimit-LimitundX-RateLimit-Remaining(Anfragen pro Minute). - Überschreitung:
429mitcode: rate_limitundRetry-After(Sekunden). Warten, nicht sofort wiederholen. - Das Monatskontingent richtet sich nach Ihrem Tarif. Ist es aufgebraucht:
429 kontingent_erschoepft. - Auffällige Nutzung (z. B. massenhaftes Abziehen aller Daten, viele Fehler) führt zu Drosselung oder Sperre der App.
- Every response contains
X-RateLimit-LimitandX-RateLimit-Remaining(requests per minute). - When exceeded:
429withcode: rate_limitandRetry-After(seconds). Wait, do not retry immediately. - The monthly quota depends on your plan. When used up:
429 kontingent_erschoepft. - Unusual usage (e.g. bulk-pulling all data, many errors) leads to throttling or suspension of the app.
FehlerErrors
Fehler kommen als application/problem+json (RFC 9457). Werten Sie code aus, nicht den Text. type verweist auf die passende Zeile dieser Tabelle.
Errors are returned as application/problem+json (RFC 9457). Evaluate code, not the text. type links to the matching row of this table.
OAuth-Endpunkte (/oauth/token, /oauth/revoke) antworten nach RFC 6749 mit {"error": "invalid_grant"} usw.
OAuth endpoints (/oauth/token, /oauth/revoke) answer per RFC 6749 with {"error": "invalid_grant"} etc.
Webhooks
Im Partnerportal legen Sie je App Webhooks an (Ziel-URL nur https, Ereignisse wählen). Das Signaturgeheimnis wird nur einmal angezeigt. Wir senden „dünne" Ereignisse – nur Typ und IDs. Die Daten holen Sie anschließend mit einem gültigen Token über die API; dabei greifen Scopes und Zustimmung erneut.
- Header:
X-SPlus-Signatur: t=<Unix-Zeit>,v1=<hex>,X-SPlus-Ereignis,X-SPlus-Zustellung. - Signatur: HMAC-SHA256 über
"<t>.<roher Body>"mit dem Geheimnis. Vergleich in konstanter Zeit, Zeittoleranz 5 Minuten (Schutz vor Wiedereinspielung). - Antworten Sie schnell mit 2xx und verarbeiten Sie asynchron. Andere Antworten oder Zeitüberschreitung gelten als Fehlschlag und werden mit wachsendem Abstand wiederholt.
- Zustellung „mindestens einmal": Ereignisse können doppelt kommen – anhand der Ereignis-
identdoppeln. Die Reihenfolge ist nicht garantiert; nutzen Siegeaendert_am.
In the partner portal you create webhooks per app (https target URL only, choose events). The signing secret is shown only once. We send "thin" events – only type and IDs. You then fetch the data with a valid token via the API; scopes and consent are checked again.
- Headers:
X-SPlus-Signatur: t=<unix time>,v1=<hex>,X-SPlus-Ereignis,X-SPlus-Zustellung. - Signature: HMAC-SHA256 over
"<t>.<raw body>"with the secret. Constant-time comparison, 5-minute tolerance (replay protection). - Respond quickly with 2xx and process asynchronously. Other responses or timeouts count as failures and are retried with increasing delay.
- At-least-once delivery: events may arrive twice – deduplicate by event
id. Order is not guaranteed; usegeaendert_am.
EreignisseEvents
…
Signatur prüfen – PHPVerify signature – PHP
Signatur prüfen – Node.jsVerify signature – Node.js
SicherheitsregelnSecurity rules
- Tokens sind kurzlebig: Access-Token 15 Minuten, Autorisierungscode 2 Minuten und nur einmal verwendbar.
- Refresh-Tokens rotieren: Jede Erneuerung liefert ein neues Refresh-Token, das alte wird ungültig. Wird ein bereits ersetztes Refresh-Token erneut benutzt, sperren wir die ganze Token-Kette (Diebstahlschutz) – der Inhaber muss neu zustimmen.
- Redirect-URIs: nur https und exakter Vergleich (keine Platzhalter, keine zusätzlichen Pfade oder Parameter).
- Geheimnisse: Client-Secret und Webhook-Geheimnis nur serverseitig, nie in Apps, Browsern oder Repositories. Bei Verdacht sofort neu erzeugen.
- Widerruf durch den Inhaber jederzeit: Danach antwortet die API mit
401 invalid_token. Löschen Sie dann die Tokens des Mandanten. - Datensparsamkeit: nur Scopes anfordern, die Sie brauchen; keine sensiblen Personaldaten über die API.
- IP-Allowlist (optional je App) und Not-Aus im Partnerportal bei Vorfällen. Melden Sie Sicherheitsvorfälle unverzüglich.
- Jeder Aufruf wird protokolliert (App, Mandant, Endpunkt, Status, Menge) – ohne Inhalte.
- Tokens are short-lived: access token 15 minutes, authorization code 2 minutes and single-use.
- Refresh tokens rotate: each refresh returns a new refresh token and invalidates the old one. If an already replaced refresh token is used again, we revoke the whole token chain (theft protection) – the owner must consent again.
- Redirect URIs: https only and exact match (no wildcards, no extra paths or parameters).
- Secrets: client secret and webhook secret only server-side, never in apps, browsers or repositories. Regenerate immediately if in doubt.
- Owners can revoke at any time: afterwards the API answers
401 invalid_token. Then delete that tenant's tokens. - Data minimisation: request only the scopes you need; no sensitive staff data via the API.
- IP allowlist (optional per app) and kill switch in the partner portal for incidents. Report security incidents without delay.
- Every call is logged (app, tenant, endpoint, status, volume) – without content.
Sandbox, Live und KostenSandbox, live and pricing
- Sandbox-Pflicht: Jede neue App startet in der Sandbox und erreicht nur Demo-Mandanten mit Testdaten. Die Demo-Firmen-ID steht im Partnerportal.
- Live-Freigabe: nach einer Mindestzahl erfolgreicher Sandbox-Aufrufe und mit gebuchtem Tarif beantragen; der Betreiber entscheidet. Schreibende Scopes werden gesondert geprüft.
- Kosten: Die Nutzung ist kostenpflichtig (Tarif je App mit inklusiven Aufrufen, darüber nach Verbrauch). Alle Preise netto zzgl. Umsatzsteuer, nur für Unternehmen. Bei Zahlungsverzug wird die App gesperrt; Daten bleiben erhalten.
- Rechtliches: Auftragsverarbeitungsvertrag (Art. 28 DSGVO) mit jedem Kunden, benannter Sicherheitskontakt, Angabe des Hosting-Landes.
- Sandbox first: every new app starts in the sandbox and can only reach demo tenants with test data. The demo company ID is shown in the partner portal.
- Live approval: request it after a minimum number of successful sandbox calls and with a booked plan; the operator decides. Write scopes are reviewed separately.
- Pricing: usage is paid (plan per app with included calls, usage-based above that). All prices net plus VAT, businesses only. In case of late payment the app is suspended; data is retained.
- Legal: data processing agreement (Art. 28 GDPR) with each customer, named security contact, hosting country disclosed.
VersionierungVersioning
Die Hauptversion steht im Pfad (/v1). Innerhalb einer Version kommen nur kompatible Erweiterungen hinzu (neue Endpunkte, neue optionale Felder, neue Ereignisse). Abgekündigte Endpunkte senden Deprecation und Sunset; zwischen Abkündigung und Abschaltung liegen mindestens 6 Monate, und wir informieren Ihren Ansprechpartner per E-Mail. Der Header Idempotency-Key ist für künftige schreibende Endpunkte reserviert.
The major version is part of the path (/v1). Within a version only compatible additions are made (new endpoints, new optional fields, new events). Deprecated endpoints send Deprecation and Sunset; there are at least 6 months between deprecation and removal, and we inform your contact by e-mail. The Idempotency-Key header is reserved for future write endpoints.