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.
| Scope | Uprawnienia |
|---|---|
| vouchers:read | Odczyt voucherów |
| vouchers:write | Zarządzanie voucherami |
| bookings:read | Odczyt rezerwacji |
| bookings:write | Zarządzanie rezerwacjami |
| availability:read | Odczyt dostępności i usług |
| clients:read | Odczyt 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."
}
}| Status | code | Znaczenie |
|---|---|---|
| 400 | validation_error | Request body/query/path failed schema validation. |
| 401 | unauthorized | Missing, malformed, revoked, expired or unrecognised API key. |
| 403 | forbidden | The API key is valid but lacks the required scope. |
| 404 | {resource}_not_found | The resource does not exist, or isn't owned by this key. |
| 409 | {resource}_conflict | A concurrent write raced this request. Retry. |
| 413 | payload_too_large | Request body exceeds 64KB. |
| 429 | rate_limited | More than 120 requests/minute for this key. |
| 500 | internal_error | Unexpected server error. |
Vouchery
Wystawianie, wyszukiwanie, realizacja i listowanie voucherów prezentowych.
/api/v1/vouchersvouchers:readList 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)
/api/v1/vouchersvouchers:writeIssue 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)
/api/v1/vouchers/{code}vouchers:readLook up a voucher by code
Parametry
| Name | In | Required | Type |
|---|---|---|---|
| code | path | yes | string |
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)
/api/v1/vouchers/{code}/redeemvouchers:writeRedeem a voucher (fully, or a partial amount for VALUE vouchers)
Parametry
| Name | In | Required | Type |
|---|---|---|---|
| code | path | yes | string |
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.
/api/v1/bookingsbookings:writeCreate 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)
/api/v1/bookings/{id}bookings:readGet a booking by id
Parametry
| Name | In | Required | Type |
|---|---|---|---|
| id | path | yes | string |
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)
/api/v1/bookings/{id}/cancelbookings:writeCancel a booking
Parametry
| Name | In | Required | Type |
|---|---|---|---|
| id | path | yes | string |
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.
/api/v1/servicesavailability:readList 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)
/api/v1/availabilityavailability:readPer-day free/busy over a date range (max 90 days)
Parametry
| Name | In | Required | Type |
|---|---|---|---|
| from | query | yes | string |
| to | query | yes | string |
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.
/api/v1/clientsclients:readList/search the photographer's clients
Parametry
| Name | In | Required | Type |
|---|---|---|---|
| search | query | no | string |
| tagIds | query | no | string |
| page | query | no | integer |
| limit | query | no | integer |
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.createdbooking.cancelledvoucher.issuedvoucher.redeemedorder.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.