Sourevo
Entwickler

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:

  1. Produkte: Legen Sie jeden EUDR-Artikel mit seinem KN-Code an (bei Holz mit dem wissenschaftlichen Namen).
  2. Lieferanten: Synchronisieren Sie Ihre Kontakte mit ihrer externalRef und laden Sie sie in ihr Portal ein; verfolgen Sie ihre Vertrauensstufe.
  3. Parzellen: Der Lieferant lädt sie im Portal hoch, oder Sie laden die erhaltenen als Vorschlag hoch, den er bestätigt.
  4. Charge: Jeder Wareneingang im ERP ist eine Charge mit Menge und Herkunftsparzellen.
  5. DDS: Erstellen Sie den Entwurf aus der Charge und starten Sie die Vorprüfung (Sperren und Warnungen).
  6. Einreichung: Bestätigen Sie die Warnungen und reichen Sie bei TRACES ein.
  7. 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 18

Schlü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..." }]
    }
  }
}
HTTPcodeBedeutung
400validation_errorFehlerhafte Anfrage oder ungültiges Feld (siehe details).
400invalid_webhook_urlDie Webhook-URL ist nicht https oder zeigt auf ein internes Netz.
401missing_api_keyDer Header Authorization mit einem Schlüssel fehlt.
401invalid_api_keyDer Schlüssel existiert nicht oder wurde widerrufen.
403plan_not_allowedDer Tarif enthält die API nicht (Packs), oder Testphase bzw. Tarif sind abgelaufen.
403key_owner_forbiddenDie Person, die den Schlüssel erstellt hat, hat die Organisation verlassen oder ist weder Inhaberin noch Administratorin.
403forbiddenDie Rolle hinter dem Schlüssel erlaubt das nicht, oder die Ressource gehört einer anderen Organisation.
404not_foundExistiert in Ihrer Organisation nicht.
409conflictDer Zustand der Ressource lässt es nicht zu (siehe details.reason).
409idempotency_in_progressEine andere Anfrage mit derselben Idempotency-Key läuft noch.
413payload_too_largeDer Body ist größer als 2 MB.
422idempotency_key_reusedDiese Idempotency-Key wurde bereits mit einer anderen Anfrage verwendet.
422unprocessableEine Geschäftsregel verhindert es (siehe details.reason).
429rate_limitedMehr als 120 Anfragen pro Minute mit diesem Schlüssel.
500internal_errorUnerwarteter Fehler auf unserer Seite; er wird protokolliert.
502upstream_errorTRACES war nicht erreichbar. Wiederholen Sie mit derselben Idempotency-Key.
503service_unavailableEin 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.

MethodePfadFunktion
GET/meOrganization, plan, mode and key (use it to test the connection)
GET/operatorsCompanies that submit to TRACES
GET/suppliersList suppliers
POST/suppliersCreate a supplier
GET/suppliers/{id}Get a supplier
PATCH/suppliers/{id}Update a supplier you own (partial)
GET/suppliers/{id}/statusTrust level, plots summary, account and invitation
POST/suppliers/{id}/invitationsInvite the supplier to its portal (emailed when it has an address)
GET/suppliers/{id}/plotsPlots of a supplier your organization can use
POST/suppliers/{id}/plotsUpload plots as your proposal to the supplier
GET/plots/{id}Get a plot
POST/plots/{id}/verifyVerify (approve) the plot for your organization
POST/plots/{id}/correction-requestAsk the supplier to correct the plot ({ reason })
POST/plots/{id}/satellite-checksRun a satellite deforestation check now
GET/plots/{id}/satellite-checks/latestLatest satellite check by your organization
GET/productsList products
POST/productsCreate a product (CN code; scientific name for wood)
GET/products/{id}Get a product
GET/batchesList batches
POST/batchesCreate a batch (a goods receipt) and link its plots
GET/batches/{id}Get a batch
POST/batches/{id}/plotsLink plots ({ plotIds })
DELETE/batches/{id}/plots/{plotId}Unlink a plot
GET/ddsList due diligence statements
POST/ddsCreate 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-assessmentRecord the risk assessment of a draft
GET/dds/{id}/checkPre-submission check: blocking issues and warnings
POST/dds/{id}/submitSubmit to TRACES ({ acknowledgedWarnings })
POST/dds/{id}/amendAmend a submitted DDS (activityType, countries, comment, geoLocationConfidential)
POST/dds/{id}/retractRetract a submitted DDS ({ reason })
POST/dds/{id}/refreshAsk TRACES for the current status now
GET/webhooksList webhook endpoints
POST/webhooksRegister a webhook endpoint ({ url, events?, description? })
DELETE/webhooks/{id}Delete a webhook endpoint
POST/webhooks/{id}/testSend a signed ping now
GET/webhooks/{id}/deliveriesDelivery 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.submittedEine DDS wurde bei TRACES eingereicht (über das Web, die API oder den Abgleich).
dds.status_changedEine DDS ändert ihren Status: Referenz erhalten (AVAILABLE), abgelehnt, zurückgezogen oder geändert.
plot.proposal_answeredDer Lieferant hat eine von Ihnen vorgeschlagene Parzelle bestätigt, korrigiert oder abgelehnt.
plot.satellite_flaggedEine Satellitenprüfung hat Baumbestandsverlust erkannt: bitte prüfen.
supplier.claimedDer Lieferant hat sein Konto angelegt und den von Ihnen erstellten Datensatz übernommen.
plan.quota_80Sie haben 80 % der im Tarif enthaltenen Erklärungen verbraucht.
plan.quota_100Sie 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

Node.js
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'));
}
Python
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.

Verbindung testen
curl https://sourevo.com/api/v1/me \
  -H "Authorization: Bearer svk_..." \
  -H "X-Sourevo-Client: Odoo 18"
Produkt mit KN-Code anlegen
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"}'
Lieferanten anlegen
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"}'
Parzelle als Vorschlag hochladen
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}'
Charge aus einem Wareneingang anlegen
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"]}'
DDS-Entwurf anlegen
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."}}'
Vorprüfung
curl https://sourevo.com/api/v1/dds/DDS_ID/check \
  -H "Authorization: Bearer svk_..." \
  -H "X-Sourevo-Client: Odoo 18"
Bei TRACES einreichen
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"]}'
Webhook registrieren
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"]}'
Sie starten eine Integration?

Legen Sie ein Konto an: Die 14-tägige Testphase enthält die API im Simulationsmodus.

Kostenloses Konto anlegen