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
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.
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)
client_id andclient_secret.POST /sapi/auth/token.Přístupový token je platný 15 minut; obnovovací token na 30 dní.X-Peppol-Participant-Id záhlaví v každém volání API (např. 0245:1234567890).POST /sapi/document/send s UBL XML v souladu s Peppol a obsahují klíč idempotence pro bezpečnost.GET /sapi/document/receive s kurzorovým stránkováním, načtení úplných dokumentů a potvrzení příjmu.GET /sapi/auth/token/status pro kontrolu zbývajícího času a proaktivní aktualizaci, když je to doporučeno.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
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);
}
})();
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.
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.GET /sapi/document/receive/{documentId} automaticky nepotvrdí. Musíte explicitně zavolat koncový bod potvrzení.X-Peppol-Participant-Id záhlaví na žádost.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é.