Sourevo
Programiści

API do połączenia z Twoim ERP

Wszystko, co robi aplikacja Sourevo, bez ekranu: Twój ERP zakłada produkty i dostawców, przesyła działki, zamienia każde przyjęcie towaru w partię, przygotowuje oświadczenie (DDS), składa je w TRACES i otrzymuje numer referencyjny przez webhook.

Dokument OpenAPI 3.1 · https://sourevo.com/api/v1

Przepływ krok po kroku

Zaprojektowane dla modułu Odoo, działa z każdym ERP i programem spedytora:

  1. Produkty: załóż każdy artykuł EUDR z jego kodem CN (i nazwą naukową w przypadku drewna).
  2. Dostawcy: zsynchronizuj kontakty z ich externalRef i zaproś ich do portalu; śledź ich poziom zaufania.
  3. Działki: dostawca dodaje je w portalu albo Ty przesyłasz otrzymane od niego jako propozycję do potwierdzenia.
  4. Partia: każde przyjęcie towaru w ERP to partia z ilością i działkami pochodzenia.
  5. DDS: utwórz szkic z partii i uruchom kontrolę wstępną (blokady i ostrzeżenia).
  6. Złożenie: zaakceptuj ostrzeżenia i złóż oświadczenie w TRACES.
  7. Numer referencyjny: TRACES zwraca numer referencyjny, a webhook dds.status_changed przekazuje go do Twojego ERP.

Plany i tryb symulacji

API jest w cenie planów Pro, Firma i Spedytor. Pakiety go nie obejmują: wywołanie zwraca 403 plan_not_allowed.

Podczas 14-dniowego okresu próbnego klucze działają w trybie symulacji: oświadczenia trafiają do symulowanego TRACES w Sourevo i dostają numery zaczynające się od SIM. Nigdy nie docierają do Komisji.

Każda odpowiedź ma nagłówek X-Sourevo-Mode (live lub simulated), a GET /me zwraca tryb i plan. Każde DDS ma pole simulated: true lub false.

Po zakończeniu okresu próbnego lub planu klucze zwracają 403 plan_not_allowed z details.reason trial_expired lub plan_expired.

Uwierzytelnianie

Każde zapytanie zawiera nagłówek Authorization: Bearer svk_... z kluczem Twojej organizacji.

Authorization: Bearer svk_...
X-Sourevo-Client: Odoo 18

Klucze tworzy się w aplikacji, w sekcji API i integracje, wyłącznie przez właścicieli i administratorów. Są pokazywane tylko raz: przechowujemy jedynie ich skrót.

Klucz działa w imieniu osoby, która go utworzyła. Jeśli opuści ona organizację lub przestanie być administratorem, klucz zwraca 403 key_owner_forbidden. O pierwszym użyciu każdego klucza informujemy e-mailem.

Opcjonalnie: nagłówek X-Sourevo-Client (na przykład "Odoo 18") wskazuje, który program wywołuje API; pojawia się w e-mailu o pierwszym użyciu.

Limit zapytań

Do 120 zapytań na minutę na klucz. Każda odpowiedź zawiera X-RateLimit-Limit i X-RateLimit-Remaining; po przekroczeniu 429 rate_limited z Retry-After w sekundach.

Błędy

Wszystkie błędy mają tę samą postać, po angielsku: { "error": { "code", "message", "details" } }. Opieraj logikę na code i details.reason, nigdy na treści 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..." }]
    }
  }
}
HTTPcodeZnaczenie
400validation_errorNieprawidłowe zapytanie lub błędne pole (zob. details).
400invalid_webhook_urlAdres webhooka nie jest https lub wskazuje na sieć wewnętrzną.
401missing_api_keyBrak nagłówka Authorization z kluczem.
401invalid_api_keyKlucz nie istnieje lub został unieważniony.
403plan_not_allowedPlan nie obejmuje API (Pakiety) albo okres próbny lub plan się zakończył.
403key_owner_forbiddenTwórca klucza opuścił organizację lub nie jest już właścicielem ani administratorem.
403forbiddenRola klucza na to nie pozwala albo zasób należy do innej organizacji.
404not_foundNie istnieje w Twojej organizacji.
409conflictStan zasobu na to nie pozwala (zob. details.reason).
409idempotency_in_progressInne zapytanie z tym samym Idempotency-Key wciąż trwa.
413payload_too_largeTreść przekracza 2 MB.
422idempotency_key_reusedTen Idempotency-Key został już użyty z innym zapytaniem.
422unprocessableUniemożliwia to reguła biznesowa (zob. details.reason).
429rate_limitedPonad 120 zapytań na minutę z tym kluczem.
500internal_errorNieoczekiwany błąd po naszej stronie; zostaje zapisany.
502upstream_errorNie udało się połączyć z TRACES. Ponów z tym samym Idempotency-Key.
503service_unavailableUsługa opcjonalna (kontrola satelitarna) nie jest skonfigurowana.

Powody (details.reason)

Reguły biznesowe zwracają 422 unprocessable (lub 409 conflict, 400 validation_error) z details.reason z tej zamkniętej listy, w snake_case, oraz dane potrzebne do zbudowania własnego komunikatu. Na przykład pre_submission_blocked zawiera blocking i warnings, a traces_rejected zawiera kody Komisji w 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

Idempotencja

Każdy POST przyjmuje nagłówek Idempotency-Key (do 255 znaków). Używaj go zawsze przy składaniu: jeśli połączenie zostanie przerwane, ponów z tym samym kluczem bez ryzyka podwójnego złożenia.

Ten sam klucz i to samo zapytanie w ciągu 24 godzin: zwracamy zapisaną odpowiedź z nagłówkiem Idempotent-Replayed: true, niczego nie powtarzając.

Ten sam klucz z inną treścią lub ścieżką: 422 idempotency_key_reused.

Dopóki pierwsze trwa: 409 idempotency_in_progress. Odpowiedzi 5xx nie są zapisywane: ponowienie wykona się od nowa.

Stronicowanie i identyfikatory z ERP

Listy zwracają { "data": [...], "nextCursor": "..." }. Następną stronę pobierzesz przez ?cursor=<nextCursor>; limit od 1 do 100 (domyślnie 50). Na ostatniej stronie nextCursor ma wartość null.

Dostawcy, produkty i partie przyjmują externalRef, identyfikator z Twojego ERP (na przykład przyjęcie WH/IN/00012). POST z istniejącym externalRef niczego nie tworzy: zwraca istniejący rekord z kodem 200 i duplicate: true. Można wyszukiwać przez ?externalRef=.

GET /dds filtruje po status, operatorId, batchId, internalReferenceNumber i updatedSince (do synchronizacji przez odpytywanie, jeśli nie używasz webhooków).

Endpointy

Baza: /api/v1. Pełna dokumentacja, ze wszystkimi polami i schematami, jest w dokumencie OpenAPI.

MetodaŚcieżkaCo robi
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

Webhooki

Twój ERP rejestruje swój adres przez POST /webhooks (https, nigdy sieć wewnętrzna) i otrzymuje sekret tylko raz. Każde powiadomienie to POST ze zdarzeniem w JSON.

Zdarzenia

type
dds.submittedDDS zostało złożone w TRACES (z aplikacji, przez API lub synchronizację).
dds.status_changedDDS zmienia status: otrzymano numer referencyjny (AVAILABLE), odrzucone, wycofane lub zmienione.
plot.proposal_answeredDostawca potwierdził, poprawił lub odrzucił działkę przesłaną przez Ciebie jako propozycję.
plot.satellite_flaggedKontrola satelitarna wykryła utratę pokrywy drzewnej: wymaga sprawdzenia.
supplier.claimedDostawca założył konto i przejął utworzony przez Ciebie rekord.
plan.quota_80Wykorzystano 80% oświadczeń zawartych w planie.
plan.quota_100Wykorzystano wszystkie oświadczenia w planie; kolejne są dodatkowo płatne.

POST /webhooks/{id}/test wysyła podpisane zdarzenie ping, aby sprawdzić połączenie.

Treść każdego powiadomienia: { "id", "type", "createdAt", "data" }. data zawiera obiekt w tej samej postaci co w API (dds, plot, supplier...). Nagłówki: X-Sourevo-Event, X-Sourevo-Delivery (id dostawy) i 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 }
  }
}

Podpis: X-Sourevo-Signature: t=<unix>,v1=<hex>, gdzie v1 to HMAC-SHA256 z Twoim sekretem z "<t>.<surowa treść>". Odrzucaj powiadomienia różniące się o ponad 5 minut.

Każda odpowiedź 2xx potwierdza dostawę. W przeciwnym razie ponawiamy po 1 min, 5 min, 30 min, 2 h, 6 h, 12 h i 24 h; potem dostawa ma status failed. Bez przekierowań i z limitem czasu 10 sekund.

GET /webhooks/{id}/deliveries zwraca dziennik dostaw z próbami, ostatnim kodem HTTP i błędem.

Weryfikacja podpisu

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", ""))

Przykłady curl

Zastąp svk_... swoim kluczem. Identyfikatory pochodzą z poprzednich odpowiedzi.

Sprawdzenie połączenia
curl https://sourevo.com/api/v1/me \
  -H "Authorization: Bearer svk_..." \
  -H "X-Sourevo-Client: Odoo 18"
Utworzenie produktu z kodem CN
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"}'
Utworzenie dostawcy
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"}'
Przesłanie działki jako propozycji
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}'
Utworzenie partii z przyjęcia towaru
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"]}'
Utworzenie szkicu DDS
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."}}'
Kontrola wstępna
curl https://sourevo.com/api/v1/dds/DDS_ID/check \
  -H "Authorization: Bearer svk_..." \
  -H "X-Sourevo-Client: Odoo 18"
Złożenie w TRACES
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"]}'
Rejestracja webhooka
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"]}'
Zaczynasz integrację?

Załóż konto: 14-dniowy okres próbny obejmuje API w trybie symulacji.

Załóż darmowe konto