API zur Anbindung Ihres ERP
Alles, was die Sourevo-App kann, ohne Bildschirm: Ihr ERP legt Produkte und Lieferanten an, lädt Parzellen hoch, macht aus jedem Wareneingang eine Charge, bereitet die Sorgfaltserklärung (DDS) vor, reicht sie bei TRACES ein und erhält die Referenznummer per Webhook zurück.
OpenAPI-3.1-Dokument · https://sourevo.com/api/v1
Der Ablauf Schritt für Schritt
Entwickelt für das Odoo-Modul, gültig für jedes ERP oder jede Speditionssoftware:
- Produkte: Legen Sie jeden EUDR-Artikel mit seinem KN-Code an (bei Holz mit dem wissenschaftlichen Namen).
- Lieferanten: Synchronisieren Sie Ihre Kontakte mit ihrer externalRef und laden Sie sie in ihr Portal ein; verfolgen Sie ihre Vertrauensstufe.
- Parzellen: Der Lieferant lädt sie im Portal hoch, oder Sie laden die erhaltenen als Vorschlag hoch, den er bestätigt.
- Charge: Jeder Wareneingang im ERP ist eine Charge mit Menge und Herkunftsparzellen.
- DDS: Erstellen Sie den Entwurf aus der Charge und starten Sie die Vorprüfung (Sperren und Warnungen).
- Einreichung: Bestätigen Sie die Warnungen und reichen Sie bei TRACES ein.
- Referenz: TRACES liefert die Referenznummer, und der Webhook dds.status_changed bringt sie in Ihr ERP.
Tarife und Simulationsmodus
Die API ist in den Tarifen Pro, Unternehmen und Spediteur ohne Aufpreis enthalten. Packs enthalten sie nicht: Aufrufe antworten 403 plan_not_allowed.
In der 14-tägigen Testphase arbeiten die Schlüssel im Simulationsmodus: Erklärungen gehen an das simulierte TRACES von Sourevo und erhalten Referenzen mit SIM am Anfang. Sie erreichen die Kommission nie.
Jede Antwort enthält den Header X-Sourevo-Mode (live oder simulated), und GET /me liefert Modus und Tarif. Jede DDS trägt simulated: true oder false.
Endet die Testphase oder der Tarif, antworten die Schlüssel 403 plan_not_allowed mit details.reason trial_expired oder plan_expired.
Authentifizierung
Jede Anfrage enthält den Header Authorization: Bearer svk_... mit einem Schlüssel Ihrer Organisation.
Authorization: Bearer svk_...
X-Sourevo-Client: Odoo 18Schlüssel werden in der App unter API und Integrationen erstellt, nur von Inhabern und Administratoren. Sie werden nur einmal angezeigt: Wir speichern nur ihren Fingerabdruck.
Ein Schlüssel handelt im Namen der Person, die ihn erstellt hat. Verlässt sie die Organisation oder ist sie keine Administratorin mehr, antwortet der Schlüssel 403 key_owner_forbidden. Die erste Nutzung jedes Schlüssels wird per E-Mail gemeldet.
Optional: Der Header X-Sourevo-Client (zum Beispiel "Odoo 18") nennt das aufrufende Programm; er erscheint in der E-Mail zur ersten Nutzung.
Anfragelimit
Bis zu 120 Anfragen pro Minute und Schlüssel. Jede Antwort enthält X-RateLimit-Limit und X-RateLimit-Remaining; darüber 429 rate_limited mit Retry-After in Sekunden.
Fehler
Alle Fehler haben dieselbe Form, auf Englisch: { "error": { "code", "message", "details" } }. Programmieren Sie gegen code und details.reason, nie gegen den Text von message.
HTTP/1.1 422 Unprocessable Entity
{
"error": {
"code": "unprocessable",
"message": "Every warning must be acknowledged: send their keys in acknowledgedWarnings.",
"details": {
"reason": "acknowledgement_required",
"blocking": [],
"warnings": [{ "key": "supplier:cm3s...:SUPPLIER_TRUST_BELOW_VALIDATED", "code": "SUPPLIER_TRUST_BELOW_VALIDATED", "level": "supplier", "entityId": "cm3s..." }]
}
}
}| HTTP | code | Bedeutung |
|---|---|---|
| 400 | validation_error | Fehlerhafte Anfrage oder ungültiges Feld (siehe details). |
| 400 | invalid_webhook_url | Die Webhook-URL ist nicht https oder zeigt auf ein internes Netz. |
| 401 | missing_api_key | Der Header Authorization mit einem Schlüssel fehlt. |
| 401 | invalid_api_key | Der Schlüssel existiert nicht oder wurde widerrufen. |
| 403 | plan_not_allowed | Der Tarif enthält die API nicht (Packs), oder Testphase bzw. Tarif sind abgelaufen. |
| 403 | key_owner_forbidden | Die Person, die den Schlüssel erstellt hat, hat die Organisation verlassen oder ist weder Inhaberin noch Administratorin. |
| 403 | forbidden | Die Rolle hinter dem Schlüssel erlaubt das nicht, oder die Ressource gehört einer anderen Organisation. |
| 404 | not_found | Existiert in Ihrer Organisation nicht. |
| 409 | conflict | Der Zustand der Ressource lässt es nicht zu (siehe details.reason). |
| 409 | idempotency_in_progress | Eine andere Anfrage mit derselben Idempotency-Key läuft noch. |
| 413 | payload_too_large | Der Body ist größer als 2 MB. |
| 422 | idempotency_key_reused | Diese Idempotency-Key wurde bereits mit einer anderen Anfrage verwendet. |
| 422 | unprocessable | Eine Geschäftsregel verhindert es (siehe details.reason). |
| 429 | rate_limited | Mehr als 120 Anfragen pro Minute mit diesem Schlüssel. |
| 500 | internal_error | Unerwarteter Fehler auf unserer Seite; er wird protokolliert. |
| 502 | upstream_error | TRACES war nicht erreichbar. Wiederholen Sie mit derselben Idempotency-Key. |
| 503 | service_unavailable | Ein optionaler Dienst (Satellitenprüfung) ist nicht konfiguriert. |
Gründe (details.reason)
Geschäftsregeln antworten 422 unprocessable (oder 409 conflict, 400 validation_error) mit einem details.reason aus dieser geschlossenen Liste, in snake_case, und den Daten für Ihre eigene Meldung. So enthält pre_submission_blocked blocking und warnings, und traces_rejected enthält die Codes der Kommission in rules.
acknowledgement_requiredarea_required_for_pointbatch_frozenbatch_has_live_ddsbusiness_rulecoordinates_incompleteexternal_ref_takengeojson_emptygeojson_feature_without_geometrygeojson_invalid_geometrygeojson_invalid_jsongeojson_not_wgs84geojson_polygon_invalidgeojson_polygon_not_closedgeojson_polygon_too_few_verticesgeojson_too_many_verticesgeojson_unsupported_geometrygeometry_requiredinternal_reference_takeninvalid_addressinvalid_areainvalid_cn_codeinvalid_countryinvalid_cursorinvalid_dateinvalid_emailinvalid_external_refinvalid_idempotency_keyinvalid_inputinvalid_jsoninvalid_legal_nameinvalid_limitinvalid_nameinvalid_phoneinvalid_quantityinvalid_statusinvalid_tax_idinvalid_verificationkey_already_revokedlatitude_out_of_rangelongitude_out_of_rangeno_satellite_checknot_a_draftoperator_without_credentialspack_planplan_expiredplan_limit_reachedplot_not_foundplot_rejectedplot_without_geometrypolygon_required_over_4_hapre_submission_blockedquantity_requiredreason_requiredretract_reason_requiredrole_not_allowedscientific_name_requiredstorage_unavailablesubmission_in_progresssupplier_already_claimedsupplier_not_ownedtax_id_declared_by_suppliertoo_many_webhookstraces_faulttraces_permission_deniedtraces_rejectedtrial_expiredtrial_simulated_only
Idempotenz
Jeder POST akzeptiert den Header Idempotency-Key (bis zu 255 Zeichen). Verwenden Sie ihn beim Einreichen immer: Bricht die Verbindung ab, wiederholen Sie mit demselben Schlüssel, ohne doppelt einzureichen.
Gleicher Schlüssel und gleiche Anfrage innerhalb von 24 Stunden: Wir liefern die gespeicherte Antwort mit dem Header Idempotent-Replayed: true und wiederholen nichts.
Gleicher Schlüssel mit anderem Body oder Pfad: 422 idempotency_key_reused.
Solange die erste noch läuft: 409 idempotency_in_progress. 5xx-Antworten werden nicht gespeichert: Die Wiederholung wird erneut ausgeführt.
Paginierung und Referenzen Ihres ERP
Listen liefern { "data": [...], "nextCursor": "..." }. Die nächste Seite holen Sie mit ?cursor=<nextCursor>; limit reicht von 1 bis 100 (Standard 50). Auf der letzten Seite ist nextCursor null.
Lieferanten, Produkte und Chargen akzeptieren externalRef, die ID in Ihrem ERP (zum Beispiel den Wareneingang WH/IN/00012). Ein POST mit einer bereits vorhandenen externalRef legt nichts an: Er liefert den vorhandenen Datensatz mit 200 und duplicate: true. Suche mit ?externalRef= möglich.
GET /dds filtert nach status, operatorId, batchId, internalReferenceNumber und updatedSince (zum Abgleich per Abfrage, wenn Sie keine Webhooks nutzen).
Endpunkte
Basis: /api/v1. Die vollständige Referenz mit allen Feldern und Schemas steht im OpenAPI-Dokument.
| Methode | Pfad | Funktion |
|---|---|---|
| GET | /me | Organization, plan, mode and key (use it to test the connection) |
| GET | /operators | Companies that submit to TRACES |
| GET | /suppliers | List suppliers |
| POST | /suppliers | Create a supplier |
| GET | /suppliers/{id} | Get a supplier |
| PATCH | /suppliers/{id} | Update a supplier you own (partial) |
| GET | /suppliers/{id}/status | Trust level, plots summary, account and invitation |
| POST | /suppliers/{id}/invitations | Invite the supplier to its portal (emailed when it has an address) |
| GET | /suppliers/{id}/plots | Plots of a supplier your organization can use |
| POST | /suppliers/{id}/plots | Upload plots as your proposal to the supplier |
| GET | /plots/{id} | Get a plot |
| POST | /plots/{id}/verify | Verify (approve) the plot for your organization |
| POST | /plots/{id}/correction-request | Ask the supplier to correct the plot ({ reason }) |
| POST | /plots/{id}/satellite-checks | Run a satellite deforestation check now |
| GET | /plots/{id}/satellite-checks/latest | Latest satellite check by your organization |
| GET | /products | List products |
| POST | /products | Create a product (CN code; scientific name for wood) |
| GET | /products/{id} | Get a product |
| GET | /batches | List batches |
| POST | /batches | Create a batch (a goods receipt) and link its plots |
| GET | /batches/{id} | Get a batch |
| POST | /batches/{id}/plots | Link plots ({ plotIds }) |
| DELETE | /batches/{id}/plots/{plotId} | Unlink a plot |
| GET | /dds | List due diligence statements |
| POST | /dds | Create a draft from a batch |
| GET | /dds/{id} | Get a DDS (status, reference and verification numbers) |
| PATCH | /dds/{id} | Change a draft |
| DELETE | /dds/{id} | Delete a draft |
| POST | /dds/{id}/risk-assessment | Record the risk assessment of a draft |
| GET | /dds/{id}/check | Pre-submission check: blocking issues and warnings |
| POST | /dds/{id}/submit | Submit to TRACES ({ acknowledgedWarnings }) |
| POST | /dds/{id}/amend | Amend a submitted DDS (activityType, countries, comment, geoLocationConfidential) |
| POST | /dds/{id}/retract | Retract a submitted DDS ({ reason }) |
| POST | /dds/{id}/refresh | Ask TRACES for the current status now |
| GET | /webhooks | List webhook endpoints |
| POST | /webhooks | Register a webhook endpoint ({ url, events?, description? }) |
| DELETE | /webhooks/{id} | Delete a webhook endpoint |
| POST | /webhooks/{id}/test | Send a signed ping now |
| GET | /webhooks/{id}/deliveries | Delivery log of an endpoint |
Webhooks
Ihr ERP registriert seine URL mit POST /webhooks (https, nie ein internes Netz) und erhält das Geheimnis einmalig. Jede Benachrichtigung ist ein POST mit dem Ereignis als JSON.
Ereignisse
| type | |
|---|---|
dds.submitted | Eine DDS wurde bei TRACES eingereicht (über das Web, die API oder den Abgleich). |
dds.status_changed | Eine DDS ändert ihren Status: Referenz erhalten (AVAILABLE), abgelehnt, zurückgezogen oder geändert. |
plot.proposal_answered | Der Lieferant hat eine von Ihnen vorgeschlagene Parzelle bestätigt, korrigiert oder abgelehnt. |
plot.satellite_flagged | Eine Satellitenprüfung hat Baumbestandsverlust erkannt: bitte prüfen. |
supplier.claimed | Der Lieferant hat sein Konto angelegt und den von Ihnen erstellten Datensatz übernommen. |
plan.quota_80 | Sie haben 80 % der im Tarif enthaltenen Erklärungen verbraucht. |
plan.quota_100 | Sie haben alle enthaltenen Erklärungen verbraucht; weitere werden extra berechnet. |
POST /webhooks/{id}/test sendet ein signiertes ping-Ereignis zum Testen der Verbindung.
Body jeder Benachrichtigung: { "id", "type", "createdAt", "data" }. data enthält das Objekt in derselben Form wie in der API (dds, plot, supplier...). Header: X-Sourevo-Event, X-Sourevo-Delivery (Zustellungs-ID) und X-Sourevo-Signature.
{
"id": "cm3x...",
"type": "dds.status_changed",
"createdAt": "2026-12-30T09:14:02.000Z",
"data": {
"previousStatus": "SUBMITTED",
"status": "AVAILABLE",
"dds": { "id": "cm3w...", "status": "AVAILABLE", "referenceNumber": "26ESXXXXXXXXXX", "verificationNumber": "XXXXXXXX", "simulated": false }
}
}Signatur: X-Sourevo-Signature: t=<unix>,v1=<hex>, wobei v1 der HMAC-SHA256 mit Ihrem Geheimnis über "<t>.<roher Body>" ist. Lehnen Sie Benachrichtigungen ab, die mehr als 5 Minuten abweichen.
Jede 2xx-Antwort bestätigt die Zustellung. Sonst wiederholen wir nach 1 Min., 5 Min., 30 Min., 2 Std., 6 Std., 12 Std. und 24 Std.; danach gilt die Zustellung als failed. Ohne Weiterleitungen und mit 10 Sekunden Zeitlimit.
GET /webhooks/{id}/deliveries liefert das Zustellprotokoll mit Versuchen, letztem HTTP-Status und Fehler.
Signatur prüfen
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the request body exactly as received (string).
export function isFromSourevo(secret, rawBody, header) {
const parts = Object.fromEntries(header.split(',').map((x) => x.split('=')));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const given = Buffer.from(parts.v1 ?? '', 'hex');
return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, 'hex'));
}import hashlib, hmac, time
def is_from_sourevo(secret: str, raw_body: bytes, header: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
t = int(parts.get("t", "0"))
if abs(time.time() - t) > 300:
return False
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))curl-Beispiele
Ersetzen Sie svk_... durch Ihren Schlüssel. Die IDs stammen aus den vorherigen Antworten.
curl https://sourevo.com/api/v1/me \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18"curl -X POST https://sourevo.com/api/v1/products \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18" \
-H "Content-Type: application/json" \
-d '{"description":"Green coffee","cnCode":"0901 11 00","externalRef":"product_42"}'curl -X POST https://sourevo.com/api/v1/suppliers \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18" \
-H "Content-Type: application/json" \
-d '{"name":"Finca La Esperanza","legalName":"Finca La Esperanza S.A.S.","country":"CO","taxId":"900123456","email":"finca@example.com","postalAddress":"Vereda El Placer, Pitalito, Huila","externalRef":"res.partner_7"}'curl -X POST https://sourevo.com/api/v1/suppliers/SUPPLIER_ID/plots \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18" \
-H "Content-Type: application/json" \
-d '{"name":"Lote 1","country":"CO","areaHa":2.5,"latitude":1.85,"longitude":-76.05}'curl -X POST https://sourevo.com/api/v1/batches \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18" \
-H "Content-Type: application/json" \
-d '{"productId":"PRODUCT_ID","netWeightKg":18000,"externalRef":"WH/IN/00012","plotIds":["PLOT_ID"]}'curl -X POST https://sourevo.com/api/v1/dds \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18" \
-H "Content-Type: application/json" \
-d '{"batchId":"BATCH_ID","operatorId":"OPERATOR_ID","internalReferenceNumber":"WH-IN-00012","activityType":"IMPORT","operatorRole":"OPERATOR","countryOfActivity":"ES","riskAssessment":{"conclusion":"NEGLIGIBLE","notes":"Verified plots, satellite check without loss."}}'curl https://sourevo.com/api/v1/dds/DDS_ID/check \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18"curl -X POST https://sourevo.com/api/v1/dds/DDS_ID/submit \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: submit-WH-IN-00012" \
-d '{"acknowledgedWarnings":["WARNING_KEY"]}'curl -X POST https://sourevo.com/api/v1/webhooks \
-H "Authorization: Bearer svk_..." \
-H "X-Sourevo-Client: Odoo 18" \
-H "Content-Type: application/json" \
-d '{"url":"https://erp.example.com/sourevo/webhook","events":["dds.status_changed","plot.proposal_answered"]}'Legen Sie ein Konto an: Die 14-tägige Testphase enthält die API im Simulationsmodus.
Kostenloses Konto anlegen