Poštár

Dokumentace API

SAPI-SK: Standardizované rozhraní přístupového bodu

SAPI-SK je specifikace jednotného rozhraní pro poskytovatele přístupových bodů Peppol a softwarové aplikace. Vyřešte fragmentaci trhu pomocí jediného standardizovaného API pro veškerou komunikaci Peppol.

Oficiální: sapi-sk.sk

Co je SAPI-SK?

SAPI-SK je OpenPeppol Community Initiative který standardizuje rozhraní mezi přístupovými body Peppol a obchodním softwarem. Místo toho, aby každý dodavatel implementoval vlastní integraci s každým přístupovým bodem, SAPI-SK definuje společnou specifikaci, kterou mohou všechny strany implementovat jednou a používat ji všude.

  • Pro dodavatele softwaru: Jedna integrace API funguje se všemi přístupovými body vyhovujícími SAPI-SK
  • Pro poskytovatele přístupu: Standardizovaný onboarding a konzistentní bezpečnostní model
  • Decentralizované: Každý AP/SP zůstává odpovědný za svou vlastní implementaci a provoz
  • Peppol-původní: Plná podpora pro Peppol Document Type Identifiers a Process Identifiers

Core API Endpoints

POST /sapi/auth/token

Získejte přístup a obnovte tokeny pomocí grantu OAuth 2.0 client_credentials

POST /sapi/auth/renew

Obnovte tokeny pomocí obnovovacího tokenu (zahrnuje rotaci tokenu pro zabezpečení)

GET /sapi/auth/token/status

Kontrola platnosti a vypršení platnosti tokenu (doporučení proaktivní obnovení)

POST /sapi/auth/revoke

Zrušit obnovovací token (odhlášení nebo ohrožení zabezpečení)


POST /sapi/document/send

Odešlete elektronický obchodní dokument pro doručení společnosti Peppol

GET /sapi/document/receive

Seznam přijatých dokumentů (stránkované kurzorem, nejstarší jako první)

GET /sapi/document/receive/{documentId}

Získejte konkrétní přijatý dokument s úplným obsahem

POST /sapi/document/receive/{documentId}/acknowledge

Potvrdit příjem dokumentu (idempotent, označí jako POTVRZENO)

Začínáme se SAPI-SK

  1. Zaregistrujte svou klientskou aplikaci s vaším poskytovatelem SAPI-SK Access Point a získejteclient_id andclient_secret.
  2. Ověřit: Vyměňte přihlašovací údaje za použití tokenů JWT POST /sapi/auth/token.Přístupový token je platný 15 minut; obnovovací token na 30 dní.
  3. Zadejte kontext organizace: Zahrnout X-Peppol-Participant-Id záhlaví v každém volání API (např. 0245:1234567890).
  4. Odeslat dokumenty: Použití POST /sapi/document/send s UBL XML v souladu s Peppol a obsahují klíč idempotence pro bezpečnost.
  5. Příjem dokumentů: Hlasování GET /sapi/document/receive s kurzorovým stránkováním, načtení úplných dokumentů a potvrzení příjmu.
  6. Sledovat vypršení platnosti tokenu: Použití GET /sapi/auth/token/status pro kontrolu zbývajícího času a proaktivní aktualizaci, když je to doporučeno.

Klíčové vlastnosti

Zabezpečení OAuth 2.0

Tokeny JWT s automatickým obnovením a proaktivním upozorněním na vypršení platnosti

Idempotence

Unikátní hlavička Idempotency-Key zabraňuje duplicitnímu odesílání (vypršení 24 hodin)

Stránkování kurzoru

Efektivní stránkování s neprůhlednými tokeny; dokumenty seřazené jako nejstarší

Zpracování strukturovaných chyb

Konzistentní objekty SAPIError s kategoriemi, kódy a opakovatelností

Kontrola integrity

Volitelné kontrolní součty SHA-256 pro ověření integrity datové části

Kontext pro více organizací

Připojení jednoho klienta slouží více kontextům organizace

Příklad integrace TypeScript

import { v4 as uuid } from 'uuid';

const SAPI_BASE_URL = 'https://app.peppos.cz';
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';
const PARTICIPANT_ID = '0245:1234567890'; // Your Peppol ID

let accessToken: string;
let accessTokenExpiresAt: number;

// Step 1: Authenticate
async function authenticate() {
  const response = await fetch(`${SAPI_BASE_URL}/sapi/auth/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      grant_type: 'client_credentials',
    }),
  });

  if (!response.ok) {
    throw new Error(`Auth failed: ${response.statusText}`);
  }

  const data = await response.json();
  accessToken = data.access_token;
  accessTokenExpiresAt = Date.now() + data.expires_in * 1000;
  console.log('✓ Authenticated. Token expires at:', new Date(accessTokenExpiresAt));
}

// Step 2: Check token status
async function checkTokenStatus() {
  const response = await fetch(`${SAPI_BASE_URL}/sapi/auth/token/status`, {
    headers: {
      'Authorization': `Bearer ${accessToken}`,
    },
  });

  if (!response.ok) {
    throw new Error('Token status check failed');
  }

  const data = await response.json();
  console.log('Token valid:', data.valid);
  console.log('Expires in:', data.expires_in_seconds, 'seconds');
  if (data.should_refresh) {
    console.log('⚠ Recommend proactive refresh');
  }
}

// Step 3: Send Document
async function sendDocument(invoiceXml: string) {
  const idempotencyKey = uuid();
  const response = await fetch(`${SAPI_BASE_URL}/sapi/document/send`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
      'X-Peppol-Participant-Id': PARTICIPANT_ID,
    },
    body: JSON.stringify({
      metadata: {
        documentId: 'INV-2026-0001',
        documentTypeId: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1',
        processId: 'urn:fdc:peppol.eu:2017:poacc:billing:01:1.0',
        senderParticipantId: PARTICIPANT_ID,
        receiverParticipantId: '0245:9876543210',
        creationDateTime: new Date().toISOString(),
      },
      payload: invoiceXml,
      payloadFormat: 'XML',
      payloadEncoding: 'UTF-8',
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Send failed: ${error.error.message}`);
  }

  const result = await response.json();
  console.log('✓ Document sent. Provider ID:', result.providerDocumentId);
  console.log('Status:', result.status);
  return result;
}

// Step 4: List Received Documents
async function listReceivedDocuments(pageToken?: string) {
  const queryParams = new URLSearchParams();
  if (pageToken) queryParams.append('pageToken', pageToken);
  queryParams.append('limit', '20');

  const response = await fetch(
    `${SAPI_BASE_URL}/sapi/document/receive?${queryParams}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'X-Peppol-Participant-Id': PARTICIPANT_ID,
      },
    }
  );

  if (!response.ok) {
    throw new Error('List failed');
  }

  const result = await response.json();
  console.log('✓ Received', result.documents.length, 'documents');
  return result;
}

// Step 5: Retrieve Document Details
async function getReceivedDocument(documentId: string) {
  const response = await fetch(
    `${SAPI_BASE_URL}/sapi/document/receive/${documentId}`,
    {
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'X-Peppol-Participant-Id': PARTICIPANT_ID,
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Get document failed: ${response.statusText}`);
  }

  const result = await response.json();
  console.log('✓ Document retrieved');
  console.log('XML Payload length:', result.payload.length, 'bytes');
  return result;
}

// Step 6: Acknowledge Document
async function acknowledgeDocument(documentId: string) {
  const response = await fetch(
    `${SAPI_BASE_URL}/sapi/document/receive/${documentId}/acknowledge`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'X-Peppol-Participant-Id': PARTICIPANT_ID,
      },
    }
  );

  if (!response.ok) {
    throw new Error('Acknowledge failed');
  }

  const result = await response.json();
  console.log('✓ Document acknowledged at:', result.acknowledgedDateTime);
  return result;
}

// Usage
(async () => {
  try {
    // Authenticate
    await authenticate();
    await checkTokenStatus();

    // Send invoice (example)
    const invoiceXml = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
  <cbc:ID xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">INV-001</cbc:ID>
</Invoice>`;

    const sendResult = await sendDocument(invoiceXml);

    // List received documents
    const listResult = await listReceivedDocuments();
    if (listResult.documents.length > 0) {
      const firstDoc = listResult.documents[0];
      
      // Get full document
      const fullDoc = await getReceivedDocument(firstDoc.documentId);
      console.log('Document content:', fullDoc.payload.substring(0, 200), '...');

      // Acknowledge
      await acknowledgeDocument(firstDoc.documentId);
    }
  } catch (error) {
    console.error('Error:', (error as Error).message);
  }
})();

Zpracování chyb

Všechny chyby SAPI používají konzistentní strukturu s kategoriemi pro vedení logiky opakování:

AUTH

Selhání autentizace/autorizace (401, 403, 423). Klient musí opravit přihlašovací údaje nebo seznam povolených IP adres.

VALIDATION

Chyby ověření požadavku (400, 404, 422). Chybný požadavek; NEPOKOUŠEJTE znovu beze změn.

TEMPORARY

Přechodné poruchy (429, 502, 503, 504). MUSÍTE to zopakovat s exponenciálním ústupem.

PROCESSING

Chyby zpracování na straně serveru (409, 500). Může být opakovatelný v závislosti na kontextu.

PERMANENT

Neopakovatelné poruchy. Je vyžadováno nápravné opatření; opakování nepomůže.

Každá chyba obsahuje retryable vlajka, correlation_id pro podporu a volitelné details pole pro diagnostiku na úrovni pole.

Důležité poznámky

  • Odeslání dokumentu: The POST /sapi/document/send koncový bod vrací 202 Accepted, potvrzení technického převzetí. Nezaručuje doručení ani právní účinek společnosti Peppol.
  • Žádné automatické potvrzení: Načítání dokumentů s GET /sapi/document/receive/{documentId} automaticky nepotvrdí. Musíte explicitně zavolat koncový bod potvrzení.
  • Rotace tokenu: Při obnovování tokenů je starý obnovovací token zrušen. Vždy uložte nový obnovovací token.
  • Vícenásobná organizace: Jediný ověřený klient může změnou obsluhovat více organizací a X-Peppol-Participant-Id záhlaví na žádost.
  • Idempotency Windows: Klíče idempotence jsou platné 24 hodin. Použijte jedinečné UUID, abyste zabránili náhodným duplicitám.

Poznámka: SAPI-SK je specifikace rozhraní (ne platforma nebo hub). Každý poskytovatel přístupového bodu zůstává plně odpovědný za svou vlastní implementaci, provoz, zabezpečení a shodu. Tato příručka ukazuje jednotnou smlouvu SAPI, kterou musí podporovat všichni vyhovující poskytovatelé.