Handleidingen

Foutafhandeling

De Puntjes API gebruikt de standaard HTTP-statuscodes, en geeft bij elke fout een response met dezelfde opbouw terug.


Opbouw van een foutresponse

Elke fout komt in deze vorm terug:

{
    "error": {
        "code": "ERROR_CODE",
        "message": "A human-readable description of what went wrong.",
        "status": 422,
        "request_id": "550e8400-e29b-41d4-a716-446655440000",
        "details": {}
    }
}
VeldBeschrijving
codeMachine-leesbare foutcode. Hierop laat je je integratie beslissen
messageFoutmelding voor mensen. Geschikt voor je logs, niet voor je eindgebruikers
statusHTTP-statuscode, dezelfde als die van de response zelf
request_idUniek id van dit request, voor debuggen en support
detailsExtra context, aanwezig bij validatiefouten

Dezelfde request_id komt terug in de responseheader X-Request-ID, ook bij een geslaagde response. Elke logregel die het request bij ons schrijft, bevat dat id, zodat support het request op dat id alleen terugvindt. Stuur je eigen X-Request-ID mee (letters, cijfers, ., _ en -, maximaal 128 tekens) en Puntjes gebruikt dat in plaats van er zelf een aan te maken.


HTTP-statuscodes

StatusBeschrijving
200Gelukt
201Resource aangemaakt
202Aanvaard. Het werk staat in een wachtrij en is nog niet uitgevoerd
204Gelukt, en de response heeft geen body
400Puntjes kon dit request aan geen enkele handelaar koppelen
401Unauthorized. Ongeldig of verlopen token
403Forbidden. Handelaar opgeschort of gedeactiveerd
404Resource niet gevonden
405Het pad bestaat wel, maar niet voor deze HTTP-methode
409Conflict. De resource is al geregistreerd
422Validatiefout of overtreding van een bedrijfsregel
429Rate limit of planlimiet overschreden
500Interne serverfout

Overzicht foutcodes

Authenticatiefouten

CodeStatusBeschrijving
INVALID_CLIENT401Het token is geldig, maar de OAuth-client is ingetrokken of hoort bij geen enkele handelaar
UNAUTHENTICATED401Het access token ontbreekt, is ongeldig of is verlopen

Autorisatiefouten

CodeStatusBeschrijving
FORBIDDEN403Het token is geldig, maar mag deze actie niet uitvoeren
SUBSCRIPTION_EXPIRED403Het abonnement van deze handelaar is afgelopen. Lezen antwoordt nog altijd 200; schrijven ligt stil tot de handelaar weer een plan heeft. Wachten lost dat niet op, en daarom is dit een 403 en geen 429.
VENDOR_CONTEXT_MISSING500Het token wees naar geen enkele handelaar. Geef het opnieuw uit vanaf de API-client van de handelaar zelf
VENDOR_CONTEXT_REQUIRED400Dit endpoint moet weten om welke handelaar het gaat, en het token zei dat niet
VENDOR_DEACTIVATED403Het account van de handelaar is definitief gedeactiveerd
VENDOR_INACTIVE403Het account van de handelaar staat niet in een status die API-verkeer aanvaardt
VENDOR_PENDING403Het account van de handelaar is nog niet geactiveerd
VENDOR_SUSPENDED403Het account van de handelaar is opgeschort en de respijtperiode is voorbij

Validatiefouten

CodeStatusBeschrijving
BATCH_TOO_LARGE422Een batch mag hoogstens 100 producten bevatten. Splits je catalogus op en stuur hem in stukken
VALIDATION_ERROR422De request body raakte niet door de validatie. In details staat per veld wat er misging

Fouten bij klantregistratie

CodeStatusBeschrijving
CUSTOMER_ALREADY_LINKED409De klant heeft al een andere external_id. Koppelen overschrijft nooit wat er al staat
EXTERNAL_ID_DUPLICATE409Deze external_id staat al bij deze handelaar geregistreerd
IDENTIFIER_DUPLICATE409Deze identifier staat al bij deze handelaar geregistreerd, of een andere klant claimde de gescande kaart net eerder
LOYALTY_CARD_NOT_FOUND422De loyalty_card die je meestuurt is bij deze handelaar geen vrije kaart. Scan opnieuw, of laat identifiers weg en laat Puntjes zelf een kaart uitgeven.

Resourcefouten

CodeStatusBeschrijving
BRANCH_INACTIVE422De vestiging is gedeactiveerd. Je krijgt deze code bij een nieuwe transactie, en bij een credential waarvan de standaardvestiging intussen sloot. Puntjes gaat nooit stilzwijgend verder zonder vestiging
BRANCH_NOT_FOUND422Geen enkele vestiging van jou heeft deze branch-sleutel. Puntjes kijkt ook bij je gedeactiveerde vestigingen, dus je ziet het verschil tussen een typfout en een winkel die dicht is
CUSTOMER_DEACTIVATED422De klant bestaat, maar is gedeactiveerd, dus hij kan geen punten sparen of uitgeven
CUSTOMER_HAS_NO_EMAIL422Van deze klant is geen e-mailadres bekend, dus zijn klantenkaart kan nergens naartoe
CUSTOMER_NOT_FOUND404Geen klant gevonden met deze identifier of dit ID
EXTERNAL_ID_NOT_FOUND404Geen enkele klant van jou heeft deze external_id. Verwijderde en geanonimiseerde klanten bereik je niet meer. Een gedeactiveerde klant vind je wel, en die krijgt CUSTOMER_DEACTIVATED
NO_WALLET422De klant heeft bij deze handelaar nog geen wallet, dus er is geen saldo om van af te schrijven
VOUCHER_NOT_FOUND404Geen enkele bon van jou heeft deze BON--code. Een code van een andere handelaar geeft dezelfde 404.
PRODUCT_EXTERNAL_ID_DUPLICATE409Er bestaat al een product met deze external_id. Gebruik PUT: die maakt aan of werkt bij
PRODUCT_NOT_FOUND404Geen enkel product van jou heeft deze external_id
REDEMPTION_NOT_FOUND404Geen inwisseling gevonden met deze bevestigingscode
REWARD_NOT_FOUND404Geen enkele beloning van jou heeft dit id
REWARD_UNAVAILABLE422De beloning is inactief, valt buiten de periode waarin ze beschikbaar is, of is niet gevonden
WALLET_NOT_FOUND404Deze klant heeft geen wallet, dus er valt niets te lezen of aan te passen

Bedrijfsregelfouten

CodeStatusBeschrijving
BRANCH_REQUIRED422Deze beloning of campagnebon geldt maar in een deel van je vestigingen, en je request noemde er geen enkele van: ofwel helemaal geen branch, ofwel een die niet in die lijst staat. Er wordt niets verzilverd, dus dezelfde aanroep slaagt zodra je een toegelaten vestiging meestuurt
CODE_ALREADY_USED422Deze inwisseling is al geverifieerd
CODE_EXPIRED422De bevestigingscode is verlopen
IDEMPOTENCY_KEY_CONFLICT422Deze idempotency_key is al gebruikt, voor een andere klant of een andere beloning
INSUFFICIENT_BALANCE422De klant heeft niet genoeg punten voor deze inwisseling of wallet-aanpassing
OUT_OF_STOCK422Deze beloning is niet meer op voorraad
VERIFICATION_FAILED422De code raakte niet geverifieerd, en niet omdat hij verlopen of al gebruikt is
VOUCHER_ALREADY_USED422De bon is al verbruikt. Verifiëren is precies wat hem verbruikt, dus een tweede aanroep komt hier uit. Vang dat op voordat je uit coulance nog een korting geeft.
VOUCHER_EXPIRED422De bon is voorbij zijn valid_until-datum, en die dag telt zelf nog mee. De laatste geldige dag is dus nog niet "voorbij"

Rate limiting

CodeStatusBeschrijving
CARD_SEND_THROTTLED429De klant kreeg zijn klantenkaart net al opgestuurd. Anders dan de twee andere 429-codes verdwijnt deze binnen enkele seconden. details.retry_after zegt na hoeveel.
PLAN_LIMIT_EXCEEDED429Het abonnement van de handelaar heeft zijn transactietegoed voor deze periode opgebruikt
RATE_LIMITED429Te veel requests in dit venster. Wacht even en probeer opnieuw

Requestfouten

CodeStatusBeschrijving
METHOD_NOT_ALLOWED405Het pad bestaat wel, maar niet voor deze HTTP-methode
ROUTE_NOT_FOUND404Op dit pad staat geen endpoint. Controleer de versieprefix en de spelling

Serverfouten

CodeStatusBeschrijving
INTERNAL_ERROR500Er liep iets mis aan onze kant. Probeer opnieuw, en vermeld request_id als het blijft gebeuren

Afkomstig van POST /api/v1/customers. Het volledige contract van dat request staat bij Klant-endpoints.

De identifiers-array accepteert twee types

identifiers[].type aanvaardt precies twee waarden: email en loyalty_card. Elke andere waarde krijgt een VALIDATION_ERROR (422) terug, en de melding noemt de types die wel mogen, zodat je het zelf kunt oplossen. Stuurt je client een type dat er niet meer bij hoort, dan pas je die aan. Puntjes vangt oude waarden nergens op.

Lezen verandert niet: GET /customers/lookup zoekt alleen op de waarde zelf, dus elke identifier die je klanten al dragen, blijft werken.

Aan vier codes zie je of opnieuw proberen zin heeft, en maar drie daarvan zijn een 429. Kijk dus naar de code en niet naar de status: sinds de vierde in een andere statusklasse binnenkwam, houdt de status deze gevallen niet meer uit elkaar.

RATE_LIMITED gaat vanzelf over: even wachten en opnieuw proberen. CARD_SEND_THROTTLED gaat ook vanzelf over, binnen enkele seconden, en begrenst de mailbox van één klant in plaats van jouw doorvoer. details.retry_after zegt na hoeveel seconden, en de Retry-After-header draagt hetzelfde getal. PLAN_LIMIT_EXCEEDED gaat binnen de periode niet over: het transactietegoed van het abonnement is voor deze periode op, en opnieuw proberen verbruikt alleen wat er nog rest. De volgende periode vult het weer aan, en een groter abonnement legt de grens hoger.

SUBSCRIPTION_EXPIRED is een 403, geen vierde 429. Schrijven stopt zodra het abonnement van een handelaar afloopt, terwijl lezen gewoon 200 blijft antwoorden, dus een GET krijgt deze code nooit te zien. Wachten lost het niet op: de handelaar blijft alleen-lezen tot die weer een plan heeft, en iemand moet dat plan kiezen. Een beheerder van de handelaar doet dat zelf onder Facturatie; een platformbeheerder kan er ook een namens de handelaar toewijzen. De code hoort bij dezelfde 403-familie als VENDOR_SUSPENDED en VENDOR_DEACTIVATED, met één verschil waar je rekening mee moet houden: die twee weigeren ook leesacties.


Details bij een validatiefout

Bij de foutcode VALIDATION_ERROR staat in het veld details per veld wat er misging:

{
    "error": {
        "code": "VALIDATION_ERROR",
        "message": "The given data was invalid.",
        "status": 422,
        "request_id": "...",
        "details": {
            "identifiers.0.type": ["The identifier type must be one of: email, loyalty_card."],
            "email": ["The email has already been taken."]
        }
    }
}

Elke sleutel in details is een veldnaam, en de waarde is een array met de foutmeldingen voor dat veld.


Fouten afhandelen in je integratie

async function submitTransaction(data) {
    const response = await fetch('/api/v1/transactions', {
        method: 'POST',
        headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
    });

    if (!response.ok) {
        const { error } = await response.json();

        switch (error.code) {
            case 'CUSTOMER_NOT_FOUND':
                // Vraag de gebruiker om de klant te registreren
                break;
            case 'PLAN_LIMIT_EXCEEDED':
                // Toon limietwaarschuwing, neem contact op met de leveranciersbeheerder
                break;
            case 'VALIDATION_ERROR':
                // Verwerk fouten per veld uit error.details
                break;
            default:
                // Log error.request_id voor ondersteuning
                console.error(`API error: ${error.code}`, error.request_id);
        }
    }

    return response.json();
}