OpenAPI. Die Beschreibung aller Ressourcen, zum Importieren in Postman oder zum Erzeugen eines Clients: openapi-integracion.json.
Testumgebung. Es gibt eine Sandbox mit erfundenen Daten: siehe den Abschnitt zur Testumgebung. Um Zugang anzufragen oder bei Fragen schreiben Sie uns über das Kontaktformular.
Die Turnera-Integrations-API — v1
Turnera stellt eine API bereit, damit ein externes Programm — das ERP des Zentrums oder das seiner Steuerberatung — dessen Katalog, Kunden und Verkäufe lesen kann. Die API ist lesend, bis auf eine Ausnahme: einen Platz im Terminkalender reservieren. Diese Funktion gibt es für die Kampagnenplattform eines Zentrums, die den Kunden zuführt und den Termin sofort benötigt (Abschnitt 5.1). Alles andere wird von außen nicht verändert. Und es gibt einen Rückweg: die Webhooks (Abschnitt 5.2), mit denen Turnera Ihr System benachrichtigt, wenn sich ein Termin ändert, ohne dass Sie alle paar Minuten nachfragen müssen.
1. In zwei Minuten
# 1. Die Zugangsdaten gegen ein Token tauschen (eine halbe Stunde gültig)
curl -s -X POST https://sucentro.turnera.devel.es/api/v1/integration/token \
-H 'Content-Type: application/json' \
-d '{"grant_type":"client_credentials","client_id":"...","client_secret":"..."}'
# {"access_token":"eyJ...","token_type":"Bearer","expires_in":1800,"scope":"catalog:read sales:read"}
# 2. Lesen, mit dem Token im Header
curl -s https://sucentro.turnera.devel.es/api/v1/integration/catalog/services \
-H 'Authorization: Bearer eyJ...'
Die Basisadresse ist die des Zentrums: https://<centro>.turnera.devel.es/api/v1. Zum Testen gibt es eine Sandbox mit erfundenen Daten (Abschnitt 11). Jedes Zentrum hat seine eigene Domain und seine eigene Datenbank, daher gibt es keinen Parameter, der angibt, welches Zentrum aufgerufen wird — das bestimmt die Domain.
2. Als Client registrieren
Das geschieht nicht über die API. Wer das ERP programmiert, erhält Zugangsdaten, nicht die Möglichkeit, sich weitere zu erstellen.
Das erledigt jemand mit Administrationsrolle des Zentrums unter Verwaltung → Integrationen → Neue Integration. Dort wird festgelegt:
| Feld | Was es ist |
|---|---|
| Name | Wie das Zentrum die Integration in der Liste erkennt. «ERP der Steuerberatung». |
| Kontakt-E-Mail | Die Adresse der Person, die die Integration betreut. Sie wird für Hinweise auf Wartungsarbeiten, Versionswechsel oder Störungen der API verwendet. Pflichtfeld. |
| Standort | Die einzige, die die Integration sehen wird. Sie wird hier festgelegt, weil eine Maschine sie nicht in einer Bildschirmmaske auswählen kann. |
| Berechtigungen | Was gelesen werden darf. Siehe die Scope-Tabelle weiter unten. |
| Autorisierte Adressen | Von wo aus der Zugriff erlaubt ist. Eine pro Zeile: eine IPv4-Adresse oder ein Bereich in CIDR-Notation, oder any, um nicht einzuschränken (siehe «Die IP»). |
Beim Speichern erhält der Inhaber des Zentrums eine E-Mail mit der Registrierung und ihren Daten (niemals das Geheimnis), damit es keine Integrationen gibt, von deren Existenz niemand weiß. Der Bildschirm zeigt die client_id und das client_secret ein einziges Mal an. Das Geheimnis wird nicht im Klartext gespeichert; geht es verloren, lässt es sich nicht wiederherstellen: Es wird erneuert (§6).
Was Sie vom Zentrum anfordern müssen
- Die Domain seines Turnera.
- Die Zugangsdaten, über einen Kanal, der nicht die tägliche E-Mail ist.
- Dass es die feste IP des Servers autorisiert, auf dem das ERP laufen wird. Während der Entwicklung kann das Zentrum
anyeintragen, wenn von einem Laptop gearbeitet wird, der das Netz wechselt; beim Übergang in die Produktion sollte es durch die IP des Servers ersetzt werden.
3. Authentifizierung
OAuth 2.0 client credentials. Weder der Benutzer einer Person noch ein statischer Schlüssel in einem Header.
POST /api/v1/integration/token
Content-Type: application/json
{ "grant_type": "client_credentials", "client_id": "...", "client_secret": "..." }
{
"access_token": "eyJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 1800,
"scope": "catalog:read sales:read"
}
Die Namen dieses Endpunkts stehen in snake_case, weil es die von OAuth 2.0 sind; der Rest der API verwendet camelCase.
- Das Token ist eine halbe Stunde gültig und enthält die
client_id, die Niederlassung und die Scopes. - Es gibt kein Refresh-Token: Läuft das Token ab, wird ein neues angefordert, was im ERP eine einzige Zeile ist. Sinnvoll ist es, es zu Beginn jeder Synchronisierung anzufordern und bei einem
401zu erneuern. - Jedes Problem — nicht existierende Kennung, nicht übereinstimmendes Geheimnis, nicht autorisierte Adresse, deaktivierte Integration — wird gleich beantwortet:
401 Unauthorized
{ "title": "Unauthorized", "status": 401, "error": "invalid_client", "detail": "Las credenciales no son válidas" }
Es wird absichtlich nicht unterschieden, welcher der vier Fälle vorlag. Sind die Zugangsdaten korrekt, braucht man die Details nicht; wenn sie nur ausprobiert werden, ebenfalls nicht.
Die IP
Die Adresse wird bei der Anforderung des Tokens und zusätzlich bei jedem Aufruf geprüft: Ein gestohlenes Token ist eine halbe Stunde gültig, und eine halbe Stunde von einer anderen Maschine aus ist eine halbe Stunde zu viel. Ein Aufruf von einer Adresse, die nicht in der Liste steht, wird mit 403 beantwortet.
Eine Änderung der Adressliste wird innerhalb von bis zu einer Minute wirksam.
Ohne IP-Einschränkung. Enthält die Liste any, ist der Zugriff von jeder Adresse aus möglich. Das ist für die Entwicklungsphase gedacht, wenn die integrierende Person zwischen Netzen wechselt. In diesem Fall ist die einzige Bremse das client_secret: Wer es besitzt, kann von überall lesen, was die Berechtigungen der Integration freigeben. Es empfiehlt sich, nur die Berechtigungen zu vergeben, die benötigt werden, und any beim Übergang in die Produktion durch die IP des Servers zu ersetzen.
4. Die Berechtigungen (Scopes)
Ein eigenes, kurzes Vokabular. Die Rollen von Turnera beziehen sich auf Bildschirmmasken und bedeuten für ein ERP nichts.
| Scope | Was er freigibt |
|---|---|
catalog:read |
/catalog/services, /catalog/products |
customers:read |
/customers |
sales:read |
/sales, /accounting-draft |
availability:read |
/availability |
bookings:write |
/bookings, und die Möglichkeit, Webhooks für Termine anzulegen |
customers:exists |
/customers/exists |
Der Aufruf einer Ressource ohne ihren Scope wird mit 403 beantwortet. Es gibt keinen Scope für Krankenakten, und das heißt nicht, dass er standardmäßig nicht vergeben wird: Er existiert nicht. customers:read liefert den Verwaltungsdatensatz und nichts zu Vorgeschichte, Fällen oder Verläufen.
5. Die Ressourcen
Alle Listen liefern denselben Umschlag:
{
"items": [ ... ],
"nextCursor": "412"
}
Cursor-Paginierung
Es gibt keine Seitenzahl. Wenn jemand kassiert, während das ERP gerade paginiert, verschiebt sich eine nummerierte Seite, und ein Verkauf geht verloren, ohne dass es jemand merkt; der Cursor ist die zuletzt gelesene Kennung, sodass sich das, was bereits geschehen ist, nicht verschiebt.
GET /integration/sales?limit=200
GET /integration/sales?limit=200&cursor=412
GET /integration/sales?limit=200&cursor=980 → "nextCursor": null ← Ende
limit: standardmäßig 100, maximal 500.nextCursor: nullist das Signal, dass nichts mehr übrig ist. Es gibt keine Gesamtzahl von Elementen zum Nachzählen.
Inkrementelle Synchronisierung
Alle Listen unterstützen updatedSince, im ISO-8601-Format mit Zeitzone:
GET /integration/catalog/services?updatedSince=2026-09-01T00:00:00Z
Ein Datum ohne Zeitzone zwingt dazu, zu raten, in welcher Zone wir es interpretieren, und genau dort geht der Verkauf von elf Uhr abends verloren. Deshalb wird es mit einem 400 abgelehnt.
Der Vergleich ist größer oder gleich, daher empfiehlt es sich, das höchste gesehene updatedOn zu speichern und es im nächsten Durchlauf als Wasserzeichen zu verwenden. Eine Zeile kann doppelt erscheinen; sie wird niemals übersprungen.
Im ersten Durchlauf, ohne
updatedSince, wird alles geliefert. Das ist genau das, was passieren soll.
GET /integration/catalog/services
{
"id": 41,
"name": "Sesión de fisioterapia",
"category": "Fisioterapia",
"description": null,
"price": "35.00",
"taxRate": null,
"taxExemption": "E1",
"durationInMinutes": 45,
"enabled": true,
"updatedOn": "2026-09-18T11:04:22+02:00"
}
GET /integration/catalog/products
{
"id": 7,
"name": "Crema hidratante",
"category": "Cosmética",
"brand": "…",
"reference": "…",
"price": "18.50",
"taxRate": 21,
"taxExemption": null,
"enabled": true,
"updatedOn": "2026-09-18T11:04:22+02:00"
}
Zur Mehrwertsteuer bei beiden: taxRate ist der Satz in Prozent und taxExemption der Befreiungscode der AEAT (E1 gemäß Artikel 20 sowie die übrigen aus deren Liste). Sie schließen sich gegenseitig aus: Ein befreiter Vorgang trägt keinen Satz von null Prozent, sondern den Grund, aus dem er befreit ist. Beide auf null bedeutet, dass für diese Dienstleistung oder dieses Produkt im Katalog des Zentrums die Mehrwertsteuer noch nicht festgelegt ist. Das ist kein Fehler der API: Es ist die Aufgabenliste des Zentrums, und das ERP sollte darauf hinweisen, statt 21 anzunehmen.
GET /integration/customers
Der Verwaltungsdatensatz. Niemals etwas Klinisches.
{
"id": 1204,
"taxId": "12345678Z",
"name": "…", "surname": "…",
"email": "…", "phone": null, "mobile": "…",
"address": "…", "postalCode": "…", "city": "…", "province": "…",
"updatedOn": "2026-09-19T09:12:00+02:00"
}
GET /integration/customers/exists?email=…&mobile=…
Scope customers:exists. Sie sagt nur, ob eine E-Mail-Adresse oder eine Telefonnummer zu einem Kunden des Zentrums gehört, und nichts weiter: weder den Namen noch die Kennung. Sie dient dazu, dass eine Kampagnenplattform keine Datensätze doppelt anlegt.
{ "exists": true }
Es muss email, mobile oder beides gesendet werden; wenn beides gesendet wird, genügt es, dass eines übereinstimmt. Die Telefonnummer wird anhand ihrer letzten neun Ziffern verglichen, ohne auf Leerzeichen, Bindestriche oder Vorwahl zu achten, und muss mindestens sieben haben.
Diese Abfrage hat ihr eigenes Limit, viel strenger als das allgemeine: 20 pro Minute und 2.000 pro Tag je Integration. Bei Überschreitung folgt 429 mit Retry-After. Turnera erfasst, wie stark sie genutzt wird (niemals, wonach gefragt wird), und benachrichtigt per E-Mail den Inhaber des Zentrums und den Kontakt der Integration, wenn die Nutzung ungewöhnlich erscheint: das Tageskontingent fast ausgeschöpft, viele Abfragen und kaum Treffer, oder wiederholte Versuche mit ungültigen Zugangsdaten oder Adressen. Es ist eine Abfrage, die dazu gedacht ist, Datensätze einzeln zu prüfen, nicht dazu, eine Personenliste abzuarbeiten.
GET /integration/sales
Die kassierten Besuche, mit ihren Positionen.
{
"id": 9812,
"date": "2026-09-19T17:30:00+02:00",
"customerId": 1204,
"delegationId": 1,
"total": "53.50",
"updatedOn": "2026-09-19T17:31:02+02:00",
"refundOf": null,
"lines": [
{
"id": 44120,
"description": "Sesión de fisioterapia",
"amount": "35.00",
"discountPercentage": 0,
"serviceId": 41,
"productId": null,
"taxRate": null,
"taxExemption": "E1"
}
]
}
amountenthält den Rabatt bereits; so speichert ihn die Kassierung.discountPercentagedient nur der Information.totalist die Summe der Positionen.- Eine Erstattung ist ein weiterer Verkauf, mit negativen Positionen und negativem Gesamtbetrag und
refundOf, das auf dieiddes Verkaufs verweist, den sie rückgängig macht. Der ursprüngliche Verkauf ändert sich nicht: Um zu wissen, was tatsächlich verkauft blieb, werden beide addiert. - Die Mehrwertsteuer der Position ist die, die der Katalog heute hat, nicht die vom Tag des Verkaufs. Solange Phase 2 der Buchhaltung nicht vorhanden ist, ist dies ein Datum zum Abgleich und noch kein Rechnungsbuch.
GET /integration/accounting-draft
Die zusammenfassende Buchung pro Tag und Mehrwertsteuersatz: eine Buchung pro Tag und Satz, nicht vierhundert Belegzeilen. Keine Paginierung, und beide Daten sind erforderlich.
GET /integration/accounting-draft?from=2026-09-01&to=2026-09-30
{
"from": "2026-09-01",
"to": "2026-09-30",
"disclaimer": "Borrador para revisar con su asesoría. No sustituye a sus libros.",
"entries": [
{ "day": "2026-09-01", "taxRate": 21, "taxExemption": null,
"total": "184.20", "tickets": 6, "firstSaleId": 9780, "lastSaleId": 9791 },
{ "day": "2026-09-01", "taxRate": null, "taxExemption": null,
"total": "70.00", "tickets": 2, "firstSaleId": 9782, "lastSaleId": 9788 }
]
}
Die zweite Zeile des Beispiels ist das Kassierte ohne festgelegte Mehrwertsteuer. Sie erscheint absichtlich separat, weil genau das zu prüfen ist. Der disclaimer wird in der Antwort mitgeliefert und sollte dort angezeigt werden, wo die Zahlen angezeigt werden.
5.1 Im Terminkalender reservieren
Gedacht für eine Kampagnenplattform: Sie fragt, welche Zeiten frei sind, ihr Kunde wählt eine aus, und sie reserviert sie. Hat sie in der Zwischenzeit jemand anderes belegt — die Rezeption, das Portal oder eine andere Reservierung —, antwortet Turnera, dass sie besetzt ist.
Die Plätze sind dieselben, die das Reservierungsportal des Zentrums anbietet: nur Dienstleistungen und Orte, die als online buchbar markiert sind, mit der Mindestvorlaufzeit und den Tagen im Voraus, die das Zentrum konfiguriert hat. Die Plattform wählt die Uhrzeit; Turnera setzt den ersten freien Ort und den ersten freien Mitarbeiter ein.
GET /integration/availability?serviceId=12&day=2026-10-02
Scope availability:read. Die freien Startzeiten dieses Tages für diese Dienstleistung:
{
"day": "2026-10-02",
"serviceId": 12,
"bookableUntil": "2026-11-21",
"slots": [ { "start": "10:00:00", "end": "11:00:00" }, { "start": "11:00:00", "end": "12:00:00" } ]
}
bookableUntil ist der letzte Tag, der heute reserviert werden kann: Der Terminkalender ist so viele Tage im Voraus geöffnet, wie das Zentrum konfiguriert hat, und das Fenster rückt jeden Tag von selbst vor. Eine leere Liste für einen späteren Tag bedeutet, dass der Terminkalender noch nicht geöffnet ist, nicht, dass er voll ist. Für einen Tag innerhalb der Frist bedeutet sie, dass nichts mehr frei ist oder dass diese Dienstleistung nicht online gebucht wird.
POST /integration/bookings
Scope bookings:write.
{
"serviceId": 12,
"day": "2026-10-02",
"start": "10:00",
"reference": "VIR-000123",
"customer": { "name": "Ana", "surname": "García", "phone": "600123456", "email": "ana@example.org" }
}
| Antwort | Bedeutung |
|---|---|
201 |
Reserviert. Liefert den Termin (appointmentId, reference, day, start, end, customerId). |
200 |
Diese Referenz war bereits reserviert: Es wird derselbe Termin geliefert, ohne einen weiteren Platz zu belegen. |
409 BOOKING_SLOT_TAKEN |
Der Platz ist nicht mehr frei: Ihn hat jemand anderes belegt. Fordern Sie die Verfügbarkeit erneut an. |
409 BOOKING_TOO_FAR_AHEAD |
An diesem Tag ist der Terminkalender noch nicht geöffnet (später als bookableUntil). |
409 BOOKING_EQUIPMENT_BUSY |
Die Dienstleistung benötigt ein Gerät, und zu dieser Zeit ist es belegt. Das ist nicht dasselbe wie BOOKING_SLOT_TAKEN: Der Platz existiert, aber das Gerät fehlt. Die Antwort enthält equipmentName. Probieren Sie einen anderen Platz. |
409 BOOKING_MINIMUM_NOTICE |
Die Uhrzeit ist bereits vergangen oder erfüllt die Mindestvorlaufzeit des Zentrums nicht. Die Antwort enthält minimumNoticeHours. |
400 |
Eine Angabe fehlt, oder der Kunde bringt weder Telefonnummer noch E-Mail-Adresse mit. |
- Die Referenz macht Wiederholungen sicher. Sie ist die Kennung der Reservierung in Ihrem System und muss für jede Reservierung eindeutig sein. Erreicht Sie die Antwort nicht, wiederholen Sie dieselbe Anfrage mit derselben Referenz: Sie erhalten den bereits existierenden Termin, niemals einen zweiten.
- Der Kunde. Gibt es im Zentrum bereits einen Datensatz mit dieser E-Mail-Adresse, wird der Termin diesem Datensatz zugeordnet, und der Datensatz wird nicht verändert. Andernfalls wird mit den gesendeten Daten ein neuer Datensatz angelegt.
- Er wird gekennzeichnet. In den Kommentaren des Termins stehen der Name der Integration und die Referenz, damit die Rezeption weiß, woher er kommt, und es wird gespeichert, von welcher Integration er kam, damit jeder Kanal gemessen werden kann.
- Die Benachrichtigung des Kunden versendet Turnera, wie im Portal, wenn der Kunde eine E-Mail-Adresse hat.
- Einen Termin von außen stornieren oder ändern ist noch nicht möglich: Das geschieht im Zentrum.
5.2 Webhooks: Turnera benachrichtigt Sie
Statt alle paar Minuten zu fragen, ob es neue Termine gibt, kann das Zentrum eine Adresse Ihres Systems anlegen (Verwaltung → Integrationen → Webhooks), und Turnera ruft sie auf, wenn sich ein Termin ändert. Dafür ist der Scope bookings:write in der Integration erforderlich, und jede Integration erlaubt bis zu fünf Webhooks.
Die Adresse muss https:// verwenden, öffentlich sein und ohne Benutzer und Passwort auskommen. Adressen aus internen Netzen und Weiterleitungen werden nicht akzeptiert. Nach dem Anlegen lässt sich die Adresse nicht mehr ändern: Um auf eine andere zu verweisen, wird ein weiterer Webhook angelegt. Beim Anlegen erhält der Inhaber des Zentrums eine E-Mail darüber, wohin die Daten gesendet werden.
Die Ereignisse, die abonniert werden können:
| Ereignis | Wann |
|---|---|
booking.created |
Ein Termin wird angelegt. |
booking.updated |
Ein weiterhin gültiger Termin ändert sich (Uhrzeit, Dienstleistung, Kunde …). |
booking.cancelled |
Ein Termin wird storniert oder gelöscht. |
Außerdem gibt es webhook.ping, das nicht abonniert wird: Es ist der Test, den das Zentrum von Hand über die Bildschirmmaske auslösen kann, um zu prüfen, ob seine Adresse antwortet.
Welche Termine. Standardmäßig benachrichtigt der Webhook nur über Termine, die von derselben Integration angelegt wurden. Das Zentrum kann das Häkchen entfernen, um auch die der Rezeption und des Portals zu erhalten.
Die Benachrichtigung ist ein POST mit Content-Type: application/json:
{
"event": "booking.created",
"deliveryId": "5b0c2c1e-6c1e-4d6e-9f43-0b1c0d2e3f4a",
"occurredAt": "2026-10-02T08:15:03Z",
"data": {
"appointmentId": 48213,
"reference": "VIR-000123",
"state": "booked",
"day": "2026-10-05",
"start": "10:00",
"end": "10:45",
"service": "Limpieza facial",
"customerId": 1204,
"bookedByThisIntegration": true
}
}
state ist booked oder cancelled. Hat die Integration zusätzlich den Scope customers:read, enthält data einen Block customer mit name, surname, email und mobile; ohne diesen Scope werden keinerlei Daten der Person übermittelt. deliveryId ist für jede Benachrichtigung eindeutig und ist das, was Sie verwenden müssen, um dieselbe nicht zweimal zu verarbeiten.
Die Signatur. Jede Benachrichtigung trägt diese Header:
X-Turnera-Signature: sha256=<hex>
X-Turnera-Timestamp: 1790928903
X-Turnera-Event: booking.created
X-Turnera-Delivery: 5b0c2c1e-6c1e-4d6e-9f43-0b1c0d2e3f4a
Die Signatur ist der HMAC-SHA256, hexadezimal, von timestamp + "." + Body mit dem Geheimnis des Webhooks (dem, das mit whsec_ beginnt und nur ein einziges Mal beim Anlegen oder Erneuern angezeigt wird). Prüfen Sie sie bevor Sie dem Inhalt vertrauen, mit einem Vergleich in konstanter Zeit, und weisen Sie Benachrichtigungen ab, deren X-Turnera-Timestamp älter als fünf Minuten ist: So kann niemand eine alte Benachrichtigung wiederholen, die er kopiert hat.
Was zu antworten ist, und Wiederholungen. Antworten Sie mit einem beliebigen 2xx in weniger als 10 Sekunden; der Inhalt der Antwort spielt keine Rolle. Alles andere wird mit wachsenden Wartezeiten wiederholt: 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden und 6 Stunden; antwortet das System nach dem letzten Versuch weiterhin nicht, gilt die Benachrichtigung als verloren (das Zentrum sieht sie im Protokoll der Bildschirmmaske). Weiterleitungen werden nicht befolgt. Die Benachrichtigungen werden vierzehn Tage aufbewahrt und danach gelöscht. Wird der Termin mehrmals gespeichert, ohne dass sich etwas an dem ändert, was übermittelt wird, wird nur eine einzige Benachrichtigung gesendet.
Reihenfolge und Duplikate. Die Benachrichtigungen zu demselben Termin werden der Reihe nach gesendet, aber es wird nicht garantiert, dass sie in dieser Reihenfolge ankommen, wenn eine wiederholt wird, und eine Benachrichtigung kann mehr als einmal eintreffen. Verwenden Sie deliveryId und den Status in data (nicht die Reihenfolge des Eintreffens), um zu entscheiden.
6. Das Geheimnis erneuern
Die Erneuerung erfolgt unter Verwaltung → Integrationen → Geheimnis erneuern. Beide Geheimnisse gelten gleichzeitig, bis das alte zurückgezogen wird: Das neue wird erzeugt, das ERP wird in Ruhe konfiguriert, und wenn es antwortet, wird Das alte zurückziehen gedrückt. So wird das Geheimnis gewechselt, ohne die Integration auch nur eine Minute zu unterbrechen.
7. Anfragelimit
120 Anfragen pro Minute und Integration. Jede Antwort enthält:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
Bei Überschreitung des Limits folgt 429 mit Retry-After: 60. Neben diesem allgemeinen Limit hat /customers/exists sein eigenes (20 pro Minute und 2.000 pro Tag, siehe Abschnitt 5). Ein ERP mit einer falsch gesetzten Wiederholungsschleife legt an einem Samstag ein Zentrum lahm, daher empfiehlt es sich, Retry-After zu beachten, statt sofort zu wiederholen.
8. Die Fehler
| Code | Was passiert ist |
|---|---|
400 |
Ein Parameter ist falsch: Datum ohne Zeitzone, Cursor, den wir nicht vergeben haben, to vor from. Das detail sagt es. |
401 |
Das Token fehlt, ist abgelaufen oder gehört zu einem anderen Zentrum. Bei /token: invalid_client. |
403 |
Der Scope reicht für diese Ressource nicht aus, oder der Aufruf kommt von einer nicht autorisierten Adresse. |
409 |
Beim Reservieren: Der Platz ist nicht mehr frei (BOOKING_SLOT_TAKEN), ein Gerät, das die Dienstleistung zu dieser Zeit benötigt, fehlt (BOOKING_EQUIPMENT_BUSY), der Terminkalender reicht noch nicht bis zu diesem Tag (BOOKING_TOO_FAR_AHEAD) oder die Uhrzeit erfüllt die Mindestvorlaufzeit nicht (BOOKING_MINIMUM_NOTICE). |
429 |
Anfragelimit. Warten Sie so lange, wie Retry-After angibt. |
Alle Fehlerantworten sind application/problem+json (RFC 7807).
9. Die Regeln des Vertrags
Geschrieben, bevor es Clients gab, denn danach werden sie verhandelt:
- Hinzufügen darf man ein Feld oder eine Ressource. Ein Client, der sie nicht kennt, ignoriert sie, daher darf das Parsing des ERP bei einem unerwarteten Feld nicht brechen.
- Nicht erlaubt ist es, ein Feld zu entfernen oder umzubenennen, seinen Typ zu ändern oder die Bedeutung eines bereits existierenden zu ändern. Dafür gibt es die
v2. - Ein Feld, das keinen Sinn mehr hat, wird in der OpenAPI als veraltet markiert und weiterhin gesendet.
- Die
v1wird mindestens zwölf Monate nach Veröffentlichung derv2aufrechterhalten. - Änderungen werden den Zentren mit einer aktiven Integration mitgeteilt, und an die Kontakt-E-Mail jeder Integration. Sobald es die
v2gibt, tragen die Antworten derv1die HeaderDeprecationundSunset(RFC 9745 und RFC 8594) mit dem Datum der Abschaltung, damit das ERP es erkennt, ohne eine E-Mail zu lesen.
Ein Null-Feld wird immer als null übermittelt; es verschwindet nie aus dem JSON. Das ist der Unterschied zu den Endpunkten, die die Webanwendung nutzt, und der Grund dafür, dass die Integrations-API eigene Klassen hat.
Die OpenAPI-Beschreibung dieser API ist neben diesem Leitfaden veröffentlicht, unter devel.es/turnera/api/openapi-integracion.json, zum Importieren in Postman oder zum Generieren eines Clients. Ein Test des Programms prüft bei jeder Änderung, dass diese Datei dasselbe aussagt wie der Code. Die Installationen stellen kein Swagger bereit.
10. Was Sie vor dem Start wissen sollten
- Ein stornierter Verkauf. Heute macht die Stornierung den Verkauf rückgängig; mit Phase 2 der Buchhaltung wird er berichtigt. Das ERP erfährt es in beiden Fällen über
updatedSince, aber was es sieht, ist unterschiedlich: Heute erscheint der Verkauf nicht mehr, künftig erscheint eine Berichtigungsrechnung. - Die Beträge sind Dezimalzeichenketten, keine Gleitkommazahlen. Lesen Sie sie als
decimal. Lesen Sie sie alsdouble, wird aus35.00am Ende34.999999. - Der Buchungsentwurf ist ein Entwurf. Er nummeriert und stellt nichts aus. Die Rechnungsstellung mit VeriFactu ist Phase 2 und noch nicht vorhanden.
- Datenschutz. Die Datensätze der Kunden eines Zentrums in dessen ERP zu übertragen macht den Anbieter dieses ERP zu einem weiteren Auftragsverarbeiter des Zentrums. Das Zentrum muss ihn in seinem Verzeichnis der Verarbeitungstätigkeiten führen; die Bildschirmmaske zum Anlegen einer Integration erinnert daran.
11. Die Testumgebung
Um eine Integration zu testen, ohne die Daten eines Zentrums anzufassen, gibt es eine Sandbox: ein Schein-Zentrum unter https://sandbox.turnera.devel.es/api/v1, mit erfundenen Kunden, Katalog, kassierten Verkäufen und Terminen.
- Die Zugangsdaten werden bei DEVEL angefordert, eines pro Integrator, mit den Berechtigungen, die er benötigt. Sie werden ohne IP-Einschränkung (
any) angelegt, um von einem Laptop arbeiten zu können, der das Netz wechselt. - Sie wird jede Nacht zurückgesetzt: Alles, was angelegt wurde (vor allem Reservierungen), wird gelöscht, und die Beispieldaten kehren in ihren Ausgangszustand zurück, mit den Terminen der laufenden Woche.
- Die Zugangsdaten überstehen das Zurücksetzen:
client_idund Geheimnis bleiben gültig. - Alles in diesem Leitfaden gilt unverändert; nur die Domain ändert sich. Beim Wechsel zum echten Zentrum erstellt das Zentrum neue Zugangsdaten und autorisiert die IP des Servers.