B2B Partner API

Sell HolidaySG attraction, tour and ticket inventory through your own platform — real-time availability, price-locked quotes, money-safe booking, and wallet or credit settlement. This reference explains the full integration end to end.

Base URL  •  https://holidaysg.com/api/b2b/v1

◉ Overview

A REST API over HTTPS. JSON in, JSON out. Every response uses a consistent { data, meta, errors } envelope.

The API exposes HolidaySG's bookable inventory — attractions, tours and tickets — sourced from our suppliers and normalised into a single catalog. You browse products, check live availability, lock a price, and book. Money settles through your wallet or an approved credit line. SEO/marketing content is never exposed; you receive only the fields you need to transact.

PropertyValue
Base URLhttps://holidaysg.com/api/b2b/v1
ProtocolHTTPS only · TLS 1.2+
FormatJSON request & response (UTF-8)
AuthX-Api-Key header on every request (+ HMAC signature on money-moving calls)
CurrencySGD (all prices, one currency — no FX)
VersioningPath-pinned /v1; breaking changes ship as a new version

➜ API workflow

Five steps from browsing to a confirmed ticket. Each maps to one endpoint.

1
Browse catalog
GET /products
➜
2
Check availability
GET /availability
➜
3
Lock a price
POST /quotes
➜
4
Book & pay
POST /bookings
➜
5
Fulfil & notify
webhook + GET /bookings/{id}
  1. Understand product structure and map our products to yours (Product → Option → Ticket Type).
  2. Get live availability & pricing for a date (availability) then lock the price.
  3. Provide booking information (quote + customer) to create a paid transaction (book).
  4. Receive fulfilment — the e-ticket is delivered to the customer and you are notified by webhook.

🚀 Getting access

  1. Apply for a B2B partner account. Once approved you are placed in a commission group (default 1%).
  2. We issue a sandbox API key and its signing secret. Store the secret securely — it is shown only once.
  3. Build against sandbox (no real money or inventory) and complete the certification checklist.
  4. On certification we issue a production key and, if agreed, a credit line.

Health check

Check service availability (authenticated):

curl https://holidaysg.com/api/b2b/v1/ping -H "X-Api-Key: <your-key>"

→ { "data": { "status": "ok", "service": "HolidaySG B2B API", "version": "v1" } }

Verify your key

Verify a key at any time:

curl https://holidaysg.com/api/b2b/v1/me -H "X-Api-Key: <your-key>"

→ { "data": { "partner": { "id": 12, "name": "Acme Travel", "tier": 2, "currency": "SGD" },
      "api_key": { "name": "Prod", "prefix": "hsg_b2b_ab12", "scopes": [], "last_used_at": "..." } } }

🧩 Environments

There is one base URL for everything. Sandbox vs production is decided by which key you send — not by a different host.

EnvironmentHowMoney & inventory
SandboxUse a sandbox key against the same base URLSimulated — bookings are recorded but no wallet is charged and no supplier ticket is issued
ProductionUse a production keyReal — wallet/credit is charged and a real ticket is issued
Because the host is identical, you flip an integration from test to live simply by swapping the key and its signing secret. Keep the two key pairs in separate config.

🔑 Authentication

Send your API key on every request.

X-Api-Key: hsg_b2b_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keys are stored hashed — we never see the raw key after issue. Money-moving requests additionally require a signature.

ConditionResponse
Missing / invalid / revoked / expired key, or a source IP outside your allowlist401 AUTH_UNAUTHORIZED
Valid key calling an endpoint outside its scopes403 AUTH_SCOPE_FORBIDDEN

🎚 Scopes

Each key carries a set of scopes. An empty scope list means full access (backward-compatible default); a non-empty list is strictly enforced per endpoint.

ScopeGrants
catalog:readProducts & availability
quote:writeCreate quotes
booking:readList & retrieve bookings
booking:writeCreate bookings
booking:cancelCancel bookings
statement:readStatements

✍ Request signing

Money-moving calls — POST /bookings and POST /bookings/{id}/cancel — require an HMAC-SHA256 signature (plus /signature/verify to test it).

X-Api-Timestamp: <unix seconds>
X-Api-Nonce:     <unique per request>
X-Api-Signature: hex( hmac_sha256(canonical, signing_secret) )

canonical = timestamp "\n" METHOD "\n" path "\n" nonce "\n" rawBody
  • path includes the leading slash, no query string (e.g. /api/b2b/v1/bookings).
  • rawBody is the exact JSON bytes you send — sign the string you transmit.
  • Timestamp must be within 5 minutes of server time; each nonce is single-use (replays are rejected).

Dry-run: POST /signature/verify

Validate your signing implementation with no side effects. Note that /signature/verify is itself a signed endpoint — send the same three signature headers you would on a booking, computed over this request's own canonical string (path = /api/b2b/v1/signature/verify). The server recomputes the signature and confirms the match.

POST /signature/verify
Headers: X-Api-Key, X-Api-Timestamp, X-Api-Nonce, X-Api-Signature
{ ...any JSON body (may be empty)... }

→ 200 { "data": { "verified": true,
        "echo": { "method": "POST", "path": "/api/b2b/v1/signature/verify",
                  "timestamp": "...", "nonce": "...", "body_sha256": "..." } } }
Calling /signature/verify — or any signed endpoint — without the three signature headers returns 401 SIGNATURE_MISSING. A bad signature is 401 SIGNATURE_INVALID, a timestamp more than 5 minutes off is 401 SIGNATURE_TIMESTAMP_SKEW, and a reused nonce is 409 SIGNATURE_REPLAY. All are rejected before the request runs, so /signature/verify is the safe place to shake these out.

♻ Idempotency

Booking is safe to retry. Send a unique Idempotency-Key header on POST /bookings.

Idempotency-Key: 2f9c1e77-....-per-booking-attempt
  • First call → 201 and the booking is created.
  • Any retry with the same key → 200 returning the original booking. You are never charged twice.
  • Reuse the same key only for the same logical booking. Use a fresh key for a new booking.
  • Missing the header → 400 IDEMPOTENCY_KEY_REQUIRED.
  • Same key with different quote/customer → 409 IDEMPOTENCY_KEY_MISMATCH.

⏱ Rate limits

Per-partner, by tier. Every response carries the current window.

TierRequests / min
1 (default)60
2300
3600
41200
X-RateLimit-Limit: 60     # your tier's per-minute quota
X-RateLimit-Remaining: 57
Retry-After: 12           # present on 429

Over the limit → 429 RATE_LIMIT_EXCEEDED. Back off and honour Retry-After.

A flood guard of 300 requests / minute per source IP also runs before authentication (invalid-key requests count). Tier 3–4 integrations calling from a single IP should contact us to align the per-IP guard with their tier.

✉ Envelope & pagination

Every response — success or error — has the same shape.

{ "data": ... ,
  "meta": { "request_id": "01J...", "timestamp": "2026-08-15T07:57:00+00:00" },
  "errors": [ { "code": "...", "message": "..." } ] }

Quote request_id when contacting support — it identifies the exact call in our logs.

Cursor pagination

List endpoints are cursor-paginated. meta carries the cursors; pass one back as ?cursor=.

"meta": { "per_page": 25, "next_cursor": "eyJpZCI6...", "prev_cursor": null }

GET /products?per_page=25&cursor=eyJpZCI6...

⚠ Errors

Errors return the standard envelope with a machine code in errors[].code and a matching HTTP status.

CodeHTTPMeaning
AUTH_UNAUTHORIZED401Bad / missing / IP-blocked key
AUTH_SCOPE_FORBIDDEN403Key lacks the required scope
SIGNATURE_MISSING401A signed endpoint was called without the X-Api-Timestamp/X-Api-Nonce/X-Api-Signature headers
SIGNATURE_INVALID401Signature does not match the canonical string
SIGNATURE_TIMESTAMP_SKEW401Timestamp is more than 5 minutes from server time
SIGNATURE_REPLAY409Nonce already used within the signing window
VALIDATION_FAILED422Bad or missing parameters
RATE_LIMIT_EXCEEDED429Slow down; honour Retry-After
PRODUCT_NOT_FOUND404No such product
SOURCE_UNAVAILABLE503Availability source temporarily down — retry
QUOTE_EXPIRED409Quote expired or already used
INSUFFICIENT_FUNDS402Wallet too low — no money moved
CREDIT_LIMIT_EXCEEDED402Over credit line — no money moved
BOOKING_CONFIRM_FAILED502Supplier rejected at confirm — fully refunded
BOOKING_NOT_CANCELLABLE409Already cancelled or past cut-off — no money moved
IDEMPOTENCY_KEY_REQUIRED400Missing Idempotency-Key header on POST /bookings
IDEMPOTENCY_KEY_MISMATCH409Same idempotency key used with different quote or customer
BOOKING_NOT_FOUND404No booking matches the given id for this partner
BOOKING_RESERVE_FAILED502Upstream source could not reserve — no funds debited
TRAVELLER_ANSWERS_REQUIRED422Ticket types require per-traveller details (passport name, gender, DOB) — see availability for required questions

▦ Product structure

Every product is organised as Product → Option → Ticket Type. You map these ids to your own catalog, then reference them when quoting and booking.

Product— the attraction/experience  ·  product_id: hsgp_1
└─Option— a variant (e.g. 1-Day Pass)  ·  option_id: opt_60
    └─Ticket Type— the priced unit (ADULT / CHILD)  ·  ticket_type_id: tt_94
LevelRepresentsYou use it to…
ProductThe bookable attraction, tour or ticketList & display; the unit of a catalog page
OptionA bookable variant of the product (duration, package, session)Show the choices for a product/date
Ticket TypeThe smallest priced unit within an option (pax category)Quote & book — you pass ticket_type_id + quantity
Availability tells you which options and ticket types are bookable for a given date — always drive quoting from a live availability response rather than cached ids.

▤ Catalog

GET/productsList active, bookable products. Cursor-paginated.
GET/products/{product_id}A single product with its options.

Query parameters

ParamDescription
countryoptFilter by country, e.g. Singapore
cityoptFilter by city
category_idoptFilter by category
is_bookableopttrue to return only live-bookable products
per_page / cursoroptPagination (see envelope)
curl "https://holidaysg.com/api/b2b/v1/products?country=Singapore&per_page=20" -H "X-Api-Key: <key>"
from_price in the catalog is indicative only and must not be sold on. Always take the real price from a quote.

📅 Availability

GET/products/{id}/availabilityLive bookability for a date, with option / ticket-type detail.
ParamDescription
datereqVisit date, YYYY-MM-DD, today or later
paxoptParty size to check capacity against

Returns the options and their ticket_type_ids that are bookable for that date — this is where you get the ticket_type_id to quote and book (they are not on the catalog/product record). Availability is fetched live from the source at request time — if the source is unreachable you get 503 SOURCE_UNAVAILABLE (retry), never a stale “available”.

A missing or malformed date, or a date the product does not operate on (many run only on certain weekdays), returns 422 VALIDATION_FAILED — not an empty list. Read the operating days from the product before probing, or step the date forward until availability returns available: true.
curl "https://holidaysg.com/api/b2b/v1/products/hsgp_1/availability?date=2026-08-20&pax=2" -H "X-Api-Key: <key>"

💲 Pricing model

Simple and transparent: one currency, your price only. You never see supplier nett cost or the margin — your commission is already baked into what you pay.

FieldMeaning
unit_priceYour all-in price per ticket, in SGD, with your commission applied
sub_totalunit_price × quantity for a line
totalSum of all line sub-totals — the amount debited on booking
currencyAlways SGD

There is no separate tax or FX step — total is exactly what leaves your wallet/credit. Your own selling price and margin to your customer are yours to set on top.

🔒 Quotes

A quote locks the price for 15 minutes and returns a single-use quote_id you pass to booking. This guarantees the price you saw is the price you pay.

POST/quotesPrice-lock a set of ticket types for a date.
POST /quotes
{ "product_id": "hsgp_1",
  "date": "2026-08-20",
  "items": [ { "ticket_type_id": "tt_94", "quantity": 2 } ] }

→ { "data": {
     "quote_id": "q_XXXX", "product_id": "hsgp_1", "date": "2026-08-20", "currency": "SGD",
     "lines": [ { "ticket_type_id":"tt_94", "name":"ADULT", "quantity":2, "unit_price":56.41, "sub_total":112.82 } ],
     "total": 112.82,
     "expires_at": "2026-08-20T07:57:00+00:00" } }
  • A quote is consumed by exactly one successful booking; a used or expired quote → 409 QUOTE_EXPIRED.
  • Re-quote if the customer takes longer than the 15-minute window.

🔁 Booking lifecycle

One call books money-safely. Internally we debit first, then confirm with the supplier, and compensate automatically if anything fails — so you are never left paying for an unconfirmed ticket.

A
reserved
debited, confirming
➜
B
confirmed
ticket being issued
➜
C
cancelled
refunded to source
StatusHTTPMeaning
reserved—Transient — payment taken, confirming with supplier
confirmed201Booked & paid; e-ticket being fulfilled
confirmed (replay)200Idempotent retry returned the original booking
pending202Accepted; outcome being reconciled — watch the webhook
failed_pending_refund502Supplier rejected after debit; refund issued back to source
cancelled200Cancelled by you; refunded
Money safety: if confirmation fails or is ambiguous, the debit is reversed automatically. A 402 (insufficient funds / credit) means nothing was ever charged. You can rely on “charged ⇔ confirmed (or refunded)”.

🎫 Create a booking

POST/bookingsSigned + Idempotency-Key. Consumes a quote, charges wallet or credit, books the supplier.
FieldDescription
quote_idreqA live, unused quote
payment_methodoptwallet (default) or credit
customer.namereqLead traveller name
customer.emailreqWhere the e-ticket is sent
customer.phoneoptContact phone number (max 30 chars)
customer.mobile_codeoptCountry dial code, e.g. +65 (max 10 chars)
traveller_answersopt*Per-traveller details keyed by ticket type, with nested per-pax answers. Required when the product's availability response includes questions (e.g. passport name, gender, DOB). Omitting when required → 422 TRAVELLER_ANSWERS_REQUIRED.
POST /bookings
Headers: X-Api-Key, X-Api-Timestamp, X-Api-Nonce, X-Api-Signature, Idempotency-Key
{ "quote_id": "q_XXXX", "payment_method": "wallet",
  "customer": { "name": "Jane Tan", "email": "jane@example.com" } }

→ 201 { "data": { "booking_id": "b_XXXX", "status": "confirmed",
        "total": 112.82, "currency": "SGD",
        "customer": { "name": "Jane Tan", "email": "jane@example.com" },
        "note": null } }

See the lifecycle for the 200 / 202 / 402 / 502 paths.

🔎 Retrieve bookings

GET/bookingsYour bookings, newest first. Cursor-paginated.
GET/bookings/{booking_id}A single booking and its current status.

Query parameters (listing)

ParamDescription
per_pageoptResults per page, 1–100. Default 25.
cursoroptCursor from a previous response's meta.next_cursor / meta.prev_cursor.

Response fields

FieldDescription
booking_idYour reference for the booking
statusSee lifecycle statuses
total / currencyAmount charged, in SGD
customer{ name, email }
created_atISO-8601 timestamp (listing only — not present on create/show/cancel responses)
Note: The listing returns bookings in statuses reserved, confirmed, cancelled, and failed_pending_refund. Transient pending bookings are not included — poll GET /bookings/{id} by id to follow a 202 pending booking to its terminal state, or rely on the webhook.

↩ Cancel & refunds

POST/bookings/{booking_id}/cancelSigned. Cancels and refunds to the paying account.
  • 200 cancelled — refund returned to the source of payment (wallet, or credit reversed). Idempotent: cancelling an already-cancelled booking returns 200 without a second refund.
  • 409 BOOKING_NOT_CANCELLABLE — already cancelled, or past the supplier cut-off. No money moves.
Refunds always return to the account that paid: wallet bookings refund to the wallet; credit bookings reverse the credit charge. Cut-off windows follow the underlying supplier's policy for that product.

📩 Vouchers & fulfilment

HolidaySG fulfils the ticket — you don't have to.

On confirmed, the e-ticket / voucher is issued by the supplier and delivered by email to the customer.email on the booking. You can:

The booking response returns the booking record (id, status, total, customer) — it does not embed the raw QR/PDF. If your product experience needs to render the voucher in-app, ask your account manager to enable voucher-payload delivery for your key.

🔔 Webhooks

We push booking updates to your endpoint so you don't have to poll. Deliveries retry with backoff and dead-letter after repeated failures.

Event (X-Webhook-Event)Fires when
BOOKING_TRANSACTION_UPDATEA booking is confirmed, refunded, or otherwise changes state

Verifying a webhook

Each POST to your URL carries a signature over the raw request body, keyed by your webhook secret:

X-Webhook-Event:     BOOKING_TRANSACTION_UPDATE
X-Webhook-Signature: base64( hmac_sha256(rawBody, webhook_secret) )

# verify (pseudo)
expected = base64(hmac_sha256(request.rawBody, webhook_secret))
if !constant_time_equal(expected, header["X-Webhook-Signature"]) -> reject 400
  • Respond 2xx quickly to acknowledge; non-2xx (or timeout) triggers retries.
  • Treat delivery as at-least-once — dedupe on the booking id + state.

👛 Wallet & credit

Two ways to settle. Both are debited atomically at booking time.

MethodHow it works
WalletPre-fund by card; each booking debits the balance; refunds credit it back. Too low → 402 INSUFFICIENT_FUNDS (nothing charged).
CreditBook now, settle later against an approved limit; refunds reverse the charge. Over limit → 402 CREDIT_LIMIT_EXCEEDED.

Top-ups are done by card in the partner portal (not via this API — card handling stays on our PCI surface).

📄 Statements

GET/statementsYour monthly statements.
GET/statements/{YYYY-MM}One period: bookings, refunds, credit charged/settled, closing outstanding.

Statements are the source of truth for reconciliation. They are generated at month close and available for every period you were active. The listing returns the most recent 36 months.

🧪 Sandbox & certification

Build safely on a sandbox key — bookings are simulated (no real inventory, no real money) — then certify to go live.

Sandbox keyProduction key
BookingsSimulated — confirmed instantly, no supplier ticket issuedReal supplier reservation
MoneyNo wallet charge, no refund — nothing movesWallet is debited / refunded
Booking refPrefixed SBX-Real supplier reference
The host is identical — flip from test to live by swapping the API key and signing secret. Run the full flow as many times as you like at zero cost.

Certification checklist

GET/certificationYour integration checklist and whether the key is sandbox.
{ "data": { "sandbox_key": true, "certified": false, "completed": 4, "total": 7,
    "steps": [ {"key":"auth","done":true}, {"key":"catalog","done":true},
               {"key":"availability","done":true}, {"key":"quote","done":true},
               {"key":"signing","done":false}, {"key":"booking","done":false}, {"key":"cancel","done":false} ] } }
Going live: exercise every step — auth · catalog · availability · quote · signing · booking · cancel. The checklist is computed from your real request log. Once certified is true, your account manager issues a production key (and credit line, if agreed).

Sandbox test products

The sandbox contains a set of test products designed to cover all integration scenarios. Use these product ids in your sandbox calls:

Product IDNameScenario
hsgp_test_1Test Attraction — StandardStandard flow: single option, ADULT + CHILD ticket types, always available
hsgp_test_2Test Attraction — Multi-optionMultiple options (Half-Day, Full-Day, VIP), each with different ticket types
hsgp_test_3Test Attraction — Traveller detailsRequires per-traveller answers (passport name, gender, DOB) — tests traveller_answers handling
hsgp_test_4Test Attraction — Capacity limitedMax 2 pax per booking — tests capacity validation
hsgp_test_5Test Attraction — Weekday onlyAvailable Mon–Fri only — tests date validation and operating-day handling
hsgp_test_errTest Attraction — Error scenariosUsed with special inputs to trigger specific error codes (see test cases below)

Mandatory test cases

Partners must complete these test scenarios before going live. Progress is tracked automatically from your sandbox API calls.

1 — Happy path: full booking lifecycle

Complete the standard flow end to end with hsgp_test_1.

StepCallExpected result
1. AuthGET /ping or GET /me200 with partner details
2. CatalogGET /products200 with product list including hsgp_test_1
3. AvailabilityGET /products/hsgp_test_1/availability?date=<tomorrow>&pax=2200 with available: true and at least one ticket_type_id
4. QuotePOST /quotes with the returned ticket_type_id, quantity 2200 with quote_id, total, expires_at ~15 min in the future
5. SigningPOST /signature/verify with valid HMAC headers200 with verified: true
6. BookPOST /bookings (signed + Idempotency-Key) with the quote_id201 with status: "confirmed", booking_id prefixed SBX-
7. CancelPOST /bookings/{id}/cancel (signed)200 with status: "cancelled"

2 — Idempotent booking retry

StepCallExpected result
1. BookPOST /bookings with a fresh quote + Idempotency-Key: idem-test-1201 confirmed
2. RetryRepeat the exact same request with the same Idempotency-Key200 returning the original booking — no double charge

3 — Expired quote

StepCallExpected result
1. QuotePOST /quotes for hsgp_test_1200 with quote_id
2. WaitWait for the quote to expire (15 min), or use quote_id value q_expired_test—
3. BookPOST /bookings with the expired quote409 QUOTE_EXPIRED — no funds moved

4 — Insufficient funds

StepCallExpected result
1. BookPOST /bookings with a valid quote and customer name containing INSUFFICIENT (e.g. "name": "Test INSUFFICIENT")402 INSUFFICIENT_FUNDS — no funds moved

5 — Duplicate idempotency key with different payload

StepCallExpected result
1. BookPOST /bookings with Idempotency-Key: idem-conflict-1 and a valid quote201 confirmed
2. MismatchPOST /bookings with same key idem-conflict-1 but a different quote_id409 IDEMPOTENCY_KEY_MISMATCH

6 — Traveller answers required

StepCallExpected result
1. AvailabilityGET /products/hsgp_test_3/availability?date=<tomorrow>200 with questions array on ticket types (passport name, gender, DOB)
2. QuotePOST /quotes for hsgp_test_3200 with quote_id
3. Book without answersPOST /bookings without traveller_answers422 TRAVELLER_ANSWERS_REQUIRED
4. Book with answersPOST /bookings with valid traveller_answers201 confirmed

7 — Signature validation errors

ScenarioHow to triggerExpected result
Missing signatureCall POST /bookings without X-Api-Signature headers401 SIGNATURE_MISSING
Bad signatureSend a X-Api-Signature computed with the wrong secret401 SIGNATURE_INVALID
Timestamp skewSet X-Api-Timestamp to a value >5 minutes in the past401 SIGNATURE_TIMESTAMP_SKEW
ReplayRe-send the exact same X-Api-Nonce on a second signed request409 SIGNATURE_REPLAY

8 — Cancelling a non-cancellable booking

StepCallExpected result
1. Cancel twicePOST /bookings/{id}/cancel on an already-cancelled booking200 (idempotent — no second refund)
2. Past cut-offCancel a booking with customer name containing NOCANCEL409 BOOKING_NOT_CANCELLABLE — no funds moved

9 — Scope and auth errors

ScenarioHow to triggerExpected result
No API keyCall any endpoint without X-Api-Key401 AUTH_UNAUTHORIZED
Invalid keySend a made-up key value401 AUTH_UNAUTHORIZED
Missing scopeCall POST /bookings with a key that only has catalog:read403 AUTH_SCOPE_FORBIDDEN

10 — Validation and not-found errors

ScenarioHow to triggerExpected result
Invalid productGET /products/nonexistent/availability?date=2026-09-30404 PRODUCT_NOT_FOUND
Missing dateGET /products/hsgp_test_1/availability (no date param)422 VALIDATION_FAILED
Past dateGET /products/hsgp_test_1/availability?date=2020-01-01422 VALIDATION_FAILED
Unknown bookingGET /bookings/b_nonexistent404 BOOKING_NOT_FOUND
Missing idempotency keyPOST /bookings without Idempotency-Key header400 IDEMPOTENCY_KEY_REQUIRED
Automated tracking: all test scenarios are tracked from your actual API calls — no manual reporting needed. Call GET /certification at any time to see which steps remain. Once all 7 steps pass and your account manager has reviewed your integration, a production key is issued.

📘 Glossary

TermMeaning
ProductA bookable attraction, tour or ticket in the catalog
OptionA variant of a product (duration/package/session)
Ticket typeThe smallest priced unit within an option (pax category)
QuoteA 15-minute, single-use price lock
Nett costSupplier cost — never exposed to partners
CommissionYour margin band, baked into unit_price
Idempotency keyA per-attempt token that makes booking retries safe
NonceA single-use value that stops signed-request replays

Need a key, a higher tier, a credit line, or voucher-payload delivery? Contact your HolidaySG account manager.

Ask me anything!

Luna — HolidaySG

We use cookies

We use cookies to enhance your browsing experience, serve personalised content, and analyse our traffic. By clicking "Accept All", you consent to our use of cookies. Read our Cookie Policy