API reference

API Overview

The Puntjes API is a RESTful JSON API. All endpoints are scoped to the authenticated vendor and require OAuth2 bearer tokens.


Base URL

https://puntjes.app/api/v1

All endpoints documented in this reference are relative to this base URL.


Request format

  • Use Content-Type: application/json for request bodies
  • Include Authorization: Bearer {token} on every request (see Authentication)
  • All monetary amounts are in cents (e.g., 2500 = EUR 25.00)
  • All point values are integers

Response envelope

All successful responses are wrapped in a data envelope:

{
    "data": {
        "id": 1,
        "first_name": "Jan",
        "last_name": "De Vries"
    }
}

Paginated endpoints include pagination metadata:

{
  "data": [...],
  "links": {
    "first": "...",
    "last": "...",
    "prev": null,
    "next": "..."
  },
  "meta": {
    "current_page": 1,
    "last_page": 5,
    "per_page": 15,
    "total": 72
  }
}

Follow links.next as given. It carries the query parameters you sent (filters, sort order and per_page), so page 2 of a filtered result set is the same filter as page 1. Rebuilding the URL yourself from meta.current_page drops them: a walk that starts at per_page=5 silently returns 15 rows on the second request.


Error responses

Errors follow a consistent format:

{
    "error": {
        "code": "CUSTOMER_NOT_FOUND",
        "message": "No customer found with the given identifier.",
        "status": 404,
        "request_id": "550e8400-e29b-41d4-a716-446655440000"
    }
}

Validation errors (422) include a details field:

{
    "error": {
        "code": "VALIDATION_ERROR",
        "message": "The given data was invalid.",
        "status": 422,
        "request_id": "...",
        "details": {
            "email": ["The email field must be a valid email address."],
            "identifiers.0.type": ["The identifier type must be one of: email, loyalty_card."]
        }
    }
}

See Error handling for a full list of error codes.


Idempotency

Every endpoint that moves points requires an idempotency_key (max 255 characters). Repeating a request with a key you already used returns the original resource instead of processing it again, so these calls are safe to retry after a network failure or timeout:

EndpointKey is unique perA replay returns
POST /transactionsvendorThe original transaction; no points re-awarded
POST /redemptionsvendorThe original redemption; no points deducted again, stock untouched
POST /customers/{customer}/wallet/adjustcustomer's walletThe original ledger entry; balance not moved again

Use a value that identifies the operation in your own system (an order ID, or a UUID generated once per checkout and reused across retries). Random values that change on every attempt defeat the purpose.

This protects against network issues and duplicate submissions from POS systems.


Endpoint summary

MethodEndpointDescription
GET/meGet vendor branding
GET/customers/lookupLook up a customer
POST/customersRegister a customer
PUT PATCH/customers/by-external-id/{externalId}Update a customer
POST/customers/link-external-idLink an external ID
POST/customers/by-external-id/{externalId}/send-cardSend the loyalty card, keyed on your own id
POST/customers/{customer}/send-cardSend the customer their loyalty card
GET/customers/{customer}Get customer details
POST/transactionsSubmit a transaction
GET/customers/{customer}/walletGet wallet balance
GET/customers/{customer}/ledgerList ledger entries
GET/customers/{customer}/wallet-passDownload a wallet pass
GET/customers/{customer}/transactionsList customer transactions
POST/customers/{customer}/wallet/adjustAdjust wallet balance
GET/campaignsList campaigns
GET/rewardsList rewards
POST/redemptionsRedeem a reward
GET/redemptions/{code}Look up a redemption
POST/redemptions/{code}/verifyVerify a redemption
POST/vouchers/{code}/verifyVerify a voucher
GET/productsList products
POST/productsCreate a product
POST/products/batchBulk upsert products
POST/products/{externalId}/rewardCreate a reward from a product
GET/products/{externalId}Get a product
PUT/products/{externalId}Create or update a product (upsert)
PATCH/products/{externalId}Update a product
DELETE/products/{externalId}Delete a product

Get vendor branding

GET /api/v1/me

Return the branding of the vendor your token belongs to. Use it to colour a POS screen or a webshop widget the way the vendor's own customers already recognise, without hardcoding anything per client.

Example request

curl https://puntjes.app/api/v1/me \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response

{
    "data": {
        "display_name": "Koffiebar De Hoek",
        "logo_url": "https://cdn.example.com/logo.png",
        "brand_color_primary": "#4F46E5",
        "brand_color_accent": "#06B6D4"
    }
}