Dla deweloperów

Dokumentacja API

REST API dla voucherów, rezerwacji, dostępności i klientów. Adres bazowy: https://app.proofpilot.rfsdev.co.uk/api/v1

Uwierzytelnianie

Każde żądanie do /api/v1 wymaga nagłówka Bearer z Twoim kluczem API (prefiks pp_live_).

Authorization: Bearer pp_live_a1b2c3d4e5f6...

Utwórz klucz w panelu: Ustawienia -> API. Pełna wartość klucza pokazywana jest tylko raz, zaraz po utworzeniu, więc zapisz ją od razu. Każdy klucz ma własny zestaw zakresów uprawnień (poniżej).

Zakresy uprawnień

Każdy endpoint wymaga jednego konkretnego zakresu. Klucz bez wymaganego zakresu otrzyma odpowiedź 403.

ScopeUprawnienia
vouchers:readOdczyt voucherów
vouchers:writeZarządzanie voucherami
bookings:readOdczyt rezerwacji
bookings:writeZarządzanie rezerwacjami
availability:readOdczyt dostępności i usług
clients:readOdczyt klientów

Limity zapytań

Każdy klucz API jest ograniczony do 120 żądań na minutę. Po przekroczeniu limitu API zwraca 429 z nagłówkiem Retry-After (w sekundach).

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Retry after 42 seconds."
  }
}

Kody błędów

Każda odpowiedź błędu ma tę samą kopertę: stabilny, czytelny dla maszyny kod oraz komunikat dla człowieka.

{
  "error": {
    "code": "voucher_not_found",
    "message": "No voucher with that code."
  }
}
StatuscodeZnaczenie
400validation_errorRequest body/query/path failed schema validation.
401unauthorizedMissing, malformed, revoked, expired or unrecognised API key.
403forbiddenThe API key is valid but lacks the required scope.
404{resource}_not_foundThe resource does not exist, or isn't owned by this key.
409{resource}_conflictA concurrent write raced this request. Retry.
413payload_too_largeRequest body exceeds 64KB.
429rate_limitedMore than 120 requests/minute for this key.
500internal_errorUnexpected server error.

Vouchery

Wystawianie, wyszukiwanie, realizacja i listowanie voucherów prezentowych.

GET/api/v1/vouchersvouchers:read

List the authenticated photographer's vouchers

Przykładowa odpowiedź

{
  "vouchers": [
    {
      "id": "clx1v9f3g0000ab12cd34ef56",
      "code": "WICKED-GIFT-4F2A",
      "type": "VALUE",
      "source": "ISSUED",
      "status": "ACTIVE",
      "packageId": null,
      "packageName": null,
      "value": 100,
      "remainingValue": 65,
      "currency": "EUR",
      "expiresAt": "2027-07-18T00:00:00.000Z",
      "redeemedAt": null,
      "purchaserName": null
    }
  ]
}

200 A list of vouchers.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

POST/api/v1/vouchersvouchers:write

Issue a voucher

Treść żądania (JSON Schema)

{
  "type": "object",
  "properties": {
    "type": {
      "type": "string",
      "enum": [
        "PACKAGE",
        "VALUE"
      ]
    },
    "packageId": {
      "type": "string"
    },
    "value": {
      "type": "number",
      "exclusiveMinimum": 0,
      "maximum": 1000000
    },
    "currency": {
      "type": "string",
      "enum": [
        "PLN",
        "EUR",
        "USD",
        "GBP"
      ]
    },
    "recipientName": {
      "type": "string",
      "maxLength": 200
    },
    "recipientEmail": {
      "type": "string",
      "format": "email"
    },
    "dedication": {
      "type": "string",
      "maxLength": 1000
    },
    "expiresAt": {
      "type": "string"
    },
    "sendEmail": {
      "type": "boolean",
      "default": false
    }
  },
  "required": [
    "type"
  ],
  "additionalProperties": false
}

Przykładowa odpowiedź

{
  "id": "clx1v9f3g0000ab12cd34ef56",
  "code": "WICKED-GIFT-4F2A",
  "expiresAt": "2027-07-18T00:00:00.000Z",
  "emailQueued": true
}

201 Voucher issued.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

GET/api/v1/vouchers/{code}vouchers:read

Look up a voucher by code

Parametry

NameInRequiredType
codepathyesstring

Przykładowa odpowiedź

{
  "valid": true,
  "voucher": {
    "code": "WICKED-GIFT-4F2A",
    "type": "VALUE",
    "packageId": null,
    "packageName": null,
    "value": 100,
    "remainingValue": 65,
    "currency": "EUR",
    "expiresAt": "2027-07-18T00:00:00.000Z"
  }
}

200 The voucher's current state.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

POST/api/v1/vouchers/{code}/redeemvouchers:write

Redeem a voucher (fully, or a partial amount for VALUE vouchers)

Parametry

NameInRequiredType
codepathyesstring

Treść żądania (JSON Schema)

{
  "type": "object",
  "properties": {
    "amount": {
      "type": "number",
      "exclusiveMinimum": 0,
      "multipleOf": 0.01
    }
  },
  "additionalProperties": false
}

Przykładowa odpowiedź

{
  "id": "clx1v9f3g0000ab12cd34ef56",
  "code": "WICKED-GIFT-4F2A",
  "type": "VALUE",
  "status": "ACTIVE",
  "value": 100,
  "remainingValue": 45,
  "currency": "EUR",
  "redeemedAt": null,
  "expiresAt": "2027-07-18T00:00:00.000Z"
}

200 The voucher after redemption.

409 The voucher was redeemed concurrently - retry.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

Rezerwacje

Tworzenie, odczyt i anulowanie rezerwacji w kalendarzu fotografa.

POST/api/v1/bookingsbookings:write

Create a booking

Treść żądania (JSON Schema)

{
  "type": "object",
  "properties": {
    "packageId": {
      "type": "string"
    },
    "clientName": {
      "type": "string",
      "minLength": 1
    },
    "clientEmail": {
      "type": "string",
      "format": "email"
    },
    "clientPhone": {
      "type": "string"
    },
    "date": {
      "type": "string"
    },
    "startTime": {
      "type": "string"
    },
    "endTime": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "PENDING",
        "CONFIRMED",
        "CANCELLED",
        "EXPIRED",
        "COMPLETED"
      ],
      "default": "CONFIRMED"
    },
    "depositAmount": {
      "anyOf": [
        {
          "type": "number",
          "minimum": 0
        },
        {
          "type": "null"
        }
      ]
    },
    "depositPaid": {
      "type": "boolean",
      "default": false
    },
    "voucherCode": {
      "type": "string"
    },
    "notes": {
      "type": "string"
    },
    "clientId": {
      "type": "string"
    }
  },
  "required": [
    "packageId",
    "clientName",
    "clientEmail",
    "date",
    "startTime",
    "endTime"
  ],
  "additionalProperties": false
}

Przykładowa odpowiedź

{
  "id": "clxbk00000000000000000001",
  "packageId": "clxpkg000000000000000001",
  "clientId": null,
  "clientName": "Anna Kowalska",
  "clientEmail": "anna@example.com",
  "clientPhone": null,
  "date": "2026-08-14T00:00:00.000Z",
  "startTime": "2026-08-14T10:00:00.000Z",
  "endTime": "2026-08-14T11:00:00.000Z",
  "status": "PENDING",
  "depositAmount": null,
  "depositPaid": false,
  "voucherCode": null,
  "notes": null,
  "confirmedAt": null
}

201 Booking created.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

GET/api/v1/bookings/{id}bookings:read

Get a booking by id

Parametry

NameInRequiredType
idpathyesstring

Przykładowa odpowiedź

{
  "id": "clxbk00000000000000000001",
  "status": "CONFIRMED",
  "date": "2026-08-14",
  "startTime": "10:00",
  "endTime": "11:00",
  "clientName": "Anna Kowalska",
  "clientEmail": "anna@example.com",
  "confirmedAt": "2026-07-20T09:12:00.000Z",
  "serviceName": "Portrait session",
  "packageName": "Standard",
  "basePrice": 250,
  "discountAmount": 0,
  "voucherAmount": 0,
  "price": 250,
  "currency": "EUR",
  "photosIncluded": 20,
  "studioName": "Wicked Studio",
  "studioSlug": "wicked-studio",
  "logoUrl": null
}

200 The booking.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

POST/api/v1/bookings/{id}/cancelbookings:write

Cancel a booking

Parametry

NameInRequiredType
idpathyesstring

Przykładowa odpowiedź

{
  "id": "clxbk00000000000000000001",
  "status": "CANCELLED",
  "cancelledAt": "2026-07-20T09:20:00.000Z"
}

200 The cancelled booking.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

Dostępność i usługi

Lista rezerwowalnych usług i pakietów oraz sprawdzanie wolnych dni.

GET/api/v1/servicesavailability:read

List the photographer's bookable services and packages

Przykładowa odpowiedź

{
  "currency": "EUR",
  "services": [
    {
      "id": "clxsvc00000000000000001",
      "name": "Portrait session",
      "description": "A one-hour studio portrait session.",
      "duration": 60,
      "coverPhotos": [
        {
          "id": "clxc0000000000000000001",
          "imageUrl": "https://cdn.example.com/cover.jpg"
        }
      ],
      "packages": [
        {
          "id": "clxpkg000000000000000001",
          "name": "Standard",
          "description": "10 edited photos",
          "price": 250,
          "currency": "EUR",
          "photosIncluded": 10,
          "voucherEnabled": true,
          "requiresBooking": true
        }
      ],
      "questions": [
        {
          "id": "clxq0000000000000000001",
          "question": "Preferred location?",
          "isRequired": false
        }
      ]
    }
  ]
}

200 Currency and a list of services with their packages.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

GET/api/v1/availabilityavailability:read

Per-day free/busy over a date range (max 90 days)

Parametry

NameInRequiredType
fromqueryyesstring
toqueryyesstring

Przykładowa odpowiedź

{
  "from": "2026-08-01",
  "to": "2026-08-07",
  "days": [
    {
      "date": "2026-08-01",
      "free": true
    },
    {
      "date": "2026-08-02",
      "free": false
    }
  ]
}

200 One entry per day in range, each marked free or busy.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

Klienci

Wyszukiwanie i listowanie klientów fotografa.

GET/api/v1/clientsclients:read

List/search the photographer's clients

Parametry

NameInRequiredType
searchquerynostring
tagIdsquerynostring
pagequerynointeger
limitquerynointeger

Przykładowa odpowiedź

{
  "clients": [
    {
      "id": "clxcl0000000000000000001",
      "firstName": "Anna",
      "lastName": "Kowalska",
      "email": "anna@example.com",
      "_count": {
        "galleries": 3
      },
      "tags": [
        {
          "tag": {
            "id": "clxtag0000000000000001",
            "name": "Wedding"
          }
        }
      ]
    }
  ],
  "total": 1,
  "page": 1,
  "totalPages": 1
}

200 A page of clients.

Plus standardowe błędy: 400, 401, 403, 404, 500 (zobacz kody błędów)

Webhooks

Skonfiguruj adresy webhooków w panelu (Ustawienia -> API -> Webhooki), aby otrzymywać poniższe zdarzenia w chwili ich wystąpienia:

  • booking.created
  • booking.cancelled
  • voucher.issued
  • voucher.redeemed
  • order.paid

Każda dostawa to POST z ciałem JSON (payload zdarzenia) oraz dwoma nagłówkami:

  • X-ProofPilot-Event - nazwa zdarzenia, np. order.paid.
  • X-ProofPilot-Signature - t=<znacznik czasu unix>,v1=<hex-hmac>, gdzie hex-hmac to HMAC-SHA256 (kluczem jest sekret podpisujący adresu, pokazany raz przy tworzeniu) ciągu "<t>.<surowe ciało żądania>".

Aby zweryfikować dostawę: policz ten sam HMAC z sekretem Twojego adresu oraz otrzymanym znacznikiem czasu i ciałem żądania, porównaj go z v1 w sposób odporny na atak czasowy i odrzuć żądanie, jeśli t różni się od bieżącego czasu bardziej niż przyjęta tolerancja (kilka minut) - to właśnie zapobiega odtworzeniu przechwyconego żądania później.

import { createHmac, timingSafeEqual } from "node:crypto";

function isValidSignature(rawBody, header, secret, toleranceSeconds = 300) {
  const [tPart, v1Part] = header.split(",");
  const timestamp = Number(tPart.replace("t=", ""));
  const signature = v1Part.replace("v1=", "");

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const isFresh = Math.abs(Date.now() / 1000 - timestamp) <= toleranceSeconds;

  const expectedBuf = Buffer.from(expected);
  const signatureBuf = Buffer.from(signature);
  const isMatch =
    expectedBuf.length === signatureBuf.length &&
    timingSafeEqual(expectedBuf, signatureBuf);

  return isFresh && isMatch;
}

Dostawy ponawiane są do 5 razy z rosnącym opóźnieniem po odpowiedzi innej niż 2xx lub przekroczeniu czasu (10 s na próbę). Pełna historia prób dla każdego adresu (zdarzenie, status, kod odpowiedzi HTTP, czas) widoczna jest w panelu w sekcji Webhooki.