Blue's Affiliate API
This API enables partner systems to integrate with BlueDesk's affiliate management platform. It provides endpoints for affiliate authentication, click tracking, revenue reporting, payout management, bank-account configuration, and in-portal notifications.
The API is designed for server-to-server communication. Your backend calls these endpoints on behalf of affiliates — the affiliate portal should never call BlueDesk directly.
Authentication
All requests must include an API key in the X-API-Key header. API keys are issued by BlueDesk administrators.
X-API-Key: bdp_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Requests without a valid API key will receive a 401 Unauthorized response.
Base URL
https://affiliate-api.bluedesk.is
https://affiliate-api-staging.bluedesk.isRate Limits
| Scope | Limit | Window |
|---|---|---|
| Global (all endpoints) | 1,000 requests | Per minute |
| Login | 10 requests | Per minute per IP |
| Forgot / Reset Password | 10 requests | Per minute per IP |
| Click ingestion | 500 requests | Per minute per API key |
Exceeded limits return 429 Too Many Requests.
Rate limit exceeded and the Content-Type is text/plain; charset=utf-8. This differs from all other partner API endpoints — clients should branch on Content-Type (or status code 429) rather than always JSON-decoding the body.Error Handling
The API uses standard HTTP status codes:
| Code | Meaning |
|---|---|
200 | Success |
201 | Created |
400 | Bad request (validation error) |
401 | Unauthorized (missing/invalid API key or credentials) |
403 | Forbidden — affiliate is not in a loginable status (currently approved or active). Returned by POST /clicks when the resolved affiliate's status is unapproved, rejected, or deactivated. |
404 | Resource not found |
409 | Conflict (e.g., duplicate email) |
429 | Rate limit exceeded |
500 | Server error |
Error Response Shapes
Two error response shapes currently coexist while the API completes a transition. Both are JSON, both carry a single human-readable message, and both will be returned with the appropriate HTTP status code. Partners must handle both shapes; a future flag-day migration will unify them.
Flat (legacy) — used by endpoints that were live before the affiliate portal launched:
{
"error": "Invalid credentials"
}
Endpoints using the flat shape: POST /auth/login, POST /auth/forgot-password, POST /auth/reset-password, POST /applications, POST /affiliates/{affiliateSlug}/clicks, GET /affiliates/{affiliateSlug}/clicks, GET /affiliates/{affiliateSlug}/clicks/total, GET /affiliates/{affiliateSlug}/revenue/total, GET /affiliates/{affiliateSlug}/commission/expected, GET /affiliates/{affiliateSlug}/payouts/history, POST /affiliates/{affiliateSlug}/payouts/request.
Nested (new) — used by every endpoint added 2026-04 onwards:
{
"error": {
"message": "invalid IBAN format"
}
}
Endpoints using the nested shape: GET and PUT /affiliates/{affiliateSlug}/bank-account, GET /affiliates/{affiliateSlug}/notifications, POST /affiliates/{affiliateSlug}/notifications/mark-all-read, POST /affiliates/{affiliateSlug}/notifications/{notificationId}/read, GET /affiliates/{affiliateSlug}/performance/sub-ids.
Each endpoint section below shows the correct shape for that endpoint's error responses.
/affiliates/{affiliateSlug}/... endpoints expect the affiliate's slug — a URL-safe, stable identifier returned by POST /auth/login in the slug field. The UUID id field in the login response is internal and is not used in URLs. Unknown slugs return 404 Affiliate not found.Authenticate Affiliate
Validate affiliate credentials. Returns affiliate identity for your system to issue its own session token.
Request Body
{
"email": "affiliate@example.com",
"password": "their-password"
}
Response
200 Success
{
"affiliate": {
"id": "3f8a92b1-4e5c-4d2a-9f1b-8c7d6e5f4a3b",
"slug": "demo-affiliate",
"name": "Demo Affiliate",
"email": "affiliate@example.com",
"commissionPercentage": 10,
"referralLink": "https://bluecarrental.is?ref=demo-affiliate",
"bankAccount": {
"holderName": "Demo Affiliate ehf.",
"bankName": "Landsbankinn",
"iban": "IS140159260076545510730339",
"swift": "NBIIISRE"
}
}
}
The slug field is the URL-safe identifier you include in every per-affiliate endpoint path (e.g. /affiliates/{slug}/clicks). The id UUID is internal — informational only; do not use it as the URL key.
The commissionPercentage field is the affiliate's commission rate as a percentage (e.g. 10 means 10%). It is a number, or null when the rate has not yet been configured — distinct from 0, which would mean "zero commission". Store it client-side after login and use it to render the rate in your UI or compute per-month owed when reconciling revenue. The value only refreshes on next login; if BlueDesk changes the rate mid-session, the partner sees the old value until the affiliate signs in again.
The bankAccount field is always present in the response. It is null when the affiliate has not yet configured a bank account, allowing your system to render a "configure your account" CTA without an extra round-trip.
{
"affiliate": {
"id": "3f8a92b1-4e5c-4d2a-9f1b-8c7d6e5f4a3b",
"slug": "demo-affiliate",
"name": "Demo Affiliate",
"email": "affiliate@example.com",
"commissionPercentage": null,
"referralLink": "https://bluecarrental.is?ref=demo-affiliate",
"bankAccount": null
}
}
400 Validation error (flat error shape)
{
"error": "Email and password are required"
}
"Invalid request body" is also returned with 400 for malformed JSON.
401 Invalid credentials (flat error shape)
{
"error": "Invalid credentials"
}
Request Password Reset
Start the password reset flow for an affiliate. BlueDesk emails a single-use reset link to the affiliate's registered address.
Request Body
{
"email": "affiliate@example.com"
}
Response
200 Success — always returned for any well-formed request
{
"message": "If that email is registered, a reset link has been sent."
}
The endpoint is enumeration-safe: the same 200 response is returned whether the email matches an affiliate or not, and whether the underlying email delivery succeeded or failed. Your portal should always show a neutral confirmation screen after calling this endpoint.
400 Validation error (flat error shape)
{
"error": "Email is required"
}
Returned only when the request body is malformed or the email field is missing / not an email address.
- Only affiliates with status approved or active receive a reset email. Other statuses (unapproved, rejected, deactivated) still get the generic 200 with no side effects.
- The link in the email expires in 1 hour and can be used at most once.
- Requesting a new reset before the previous one is consumed automatically invalidates the previous link.
- Rate limit: 10 requests per minute per IP.
Complete Password Reset
Consume a reset token and set a new affiliate password. The token is the value embedded in the link sent by POST /auth/forgot-password.
Request Body
{
"token": "the-token-from-the-reset-link",
"password": "the-new-password"
}
Parameters
| Field | Required | Description |
|---|---|---|
token | Yes | The reset token from the email link. Single-use; consumed on first successful call. |
password | Yes | New password. Minimum 8 characters. |
Response
200 Success
{
"message": "Password reset successfully"
}
400 Validation or token error (flat error shape)
{
"error": "Token and password are required"
}
{
"error": "Password must be at least 8 characters"
}
{
"error": "Reset link is invalid or has expired"
}
Token failures all return the same generic message regardless of whether the token never existed, has expired, or has already been used. This prevents an attacker from probing the API to learn which tokens are valid.
500 Failed to reset password (flat error shape)
{
"error": "Failed to reset password"
}
Returned when the new password hash cannot be generated or when consuming the reset token fails for a reason other than the token being absent. Safe to retry with backoff.
- Successfully consuming a token immediately invalidates it; subsequent calls with the same token return the generic 400.
- The affiliate's password hash and
updated_atare both replaced atomically. - Rate limit: 10 requests per minute per IP.
Submit Affiliate Application
Submit a new affiliate application from the "Join Us" form. Creates an affiliate with status unapproved.
Request Body
{
"email": "new-affiliate@example.com",
"password": "securepassword123",
"name": "Demo Affiliate",
"companyName": "Nordic Travel Co",
"website": "https://nordictravel.co",
"instagram": "@nordictravel",
"tiktok": "",
"youtube": "",
"facebook": "nordictravel"
}
Parameters
| Field | Required | Description |
|---|---|---|
email | Yes | Affiliate's email address (unique) |
password | Yes | Password (min 8 characters) |
name | Yes | Affiliate's full name; doubles as the Caren join key for reservation attribution. |
companyName | No | Company or property name |
website | No | Website URL |
instagram | No | Instagram handle |
tiktok | No | TikTok handle |
youtube | No | YouTube channel |
facebook | No | Facebook page |
The name field is the single attribution key used to join clicks and revenue back to the affiliate via Caren. It must be non-empty after trimming.
Response
201 Created
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"slug": "nordic-travel-co",
"email": "new-affiliate@example.com",
"name": "Demo Affiliate",
"companyName": "Nordic Travel Co",
"status": "unapproved",
"availableBalance": 0,
"carenSetupComplete": false,
"createdAt": "2026-03-31T12:00:00Z",
"updatedAt": "2026-03-31T12:00:00Z"
}
The full Affiliate record is returned. Optional fields (e.g. companyName, website, social handles) are omitted when empty; availableBalance, carenSetupComplete, status, createdAt, and updatedAt are always present.
400 Validation error (flat error shape)
{
"error": "Name, email, and password are required"
}
"Invalid request body" is also returned with 400 for malformed JSON.
409 Email already registered (flat error shape)
{
"error": "An account with this email already exists"
}
500 Server error (flat error shape)
{
"error": "Failed to process application"
}
Server error — safe to retry with backoff.
Record Click Event
Record a referral link click. Call this whenever a visitor arrives via an affiliate's referral link.
Path Parameters
| Parameter | Description |
|---|---|
affiliateSlug | The affiliate's slug (from the slug field in the login response). URL-safe identifier; use this in the URL path. The earlier affiliateId placeholder name was renamed to remove the UUID implication. |
Request Body
All fields are optional and backward-compatible. Old clients that send only country and sourceApp continue to work unchanged.
{
"country": "IS",
"sourceApp": "website",
"subId": "promo-spring",
"source": "instagram",
"medium": "social",
"campaign": "spring-2026"
}
Parameters
| Field | Required | Description |
|---|---|---|
country | No | Visitor's ISO country code |
sourceApp | No | Identifier for the referring platform (e.g., website, app) |
subId | No | Partner's own sub-affiliate identifier — surfaces in the Sub-ID Performance endpoint |
source | No | UTM source. Stored as utm_source |
medium | No | UTM medium. Stored as utm_medium |
campaign | No | UTM campaign. Stored as utm_campaign |
source, medium, and campaign — not utmSource / utmMedium / utmCampaign.Response
201 Created
{
"status": "ok"
}
403 Affiliate is not active (flat error shape)
{
"error": "Affiliate is not active"
}
Returned when the affiliate's status is anything other than approved or active (for example unapproved, rejected, or deactivated). Clicks for paused affiliates are not recorded.
404 Affiliate not found (flat error shape)
{
"error": "Affiliate not found"
}
Returned when the {affiliateSlug} path segment does not match any affiliate's slug. This is the response you will see if you accidentally template the UUID id or the affiliate's name into the URL instead of the slug.
500 Failed to record click (flat error shape)
{
"error": "Failed to record click"
}
Server error — safe to retry with backoff.
Daily Click Breakdown
Get daily click counts for a date range. Every day in the range is included, even if the count is zero.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
from | Yes | Start date in YYYY-MM-DD format (inclusive) |
to | Yes | End date in YYYY-MM-DD format (inclusive). Must not be before from. |
Response
200 Success
{
"clicksPerDay": [
{ "date": "2026-03-01", "value": 94 },
{ "date": "2026-03-02", "value": 87 },
{ "date": "2026-03-03", "value": 0 }
]
}
Each item's date is a calendar day in YYYY-MM-DD form — not an ISO timestamp — and value is the integer click count for that day.
400 Missing or invalid date range (flat error shape)
{
"error": "from and to query parameters are required"
}
404 Affiliate not found (flat error shape) — the {affiliateSlug} path segment did not match any affiliate.
500 Server error (flat error shape)
{
"error": "Failed to get daily clicks"
}
Server error — safe to retry with backoff.
Total Clicks
Get the total click count for a date range.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
from | Yes | Start date in YYYY-MM-DD format (inclusive) |
to | Yes | End date in YYYY-MM-DD format (inclusive). Must not be before from. |
Response
200 Success
{
"total": 1234
}
400 Missing or invalid date range (flat error shape)
404 Affiliate not found (flat error shape) — the {affiliateSlug} path segment did not match any affiliate.
500 Server error (flat error shape)
{
"error": "Failed to get total clicks"
}
Server error — safe to retry with backoff.
Sub-ID Performance
Per-sub-id click breakdown for the affiliate's referral traffic. Use this to see how individual sub-affiliate identifiers and UTM combinations are performing.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
from | No | Start date in YYYY-MM-DD format (inclusive) |
to | No | End date in YYYY-MM-DD format (inclusive). Must not be before from. |
If both from and to are omitted, the endpoint defaults to the last 30 days (to = today UTC, from = today − 30 days). Supplying only one of the two still returns 400 — partial date ranges are not allowed, since one-sided defaults would silently widen a caller's query window.
Response
200 Success
[
{
"subId": "promo-spring",
"source": "instagram",
"medium": "social",
"campaign": "spring-2026",
"clicks": 142,
"bookings": null,
"conversionPercent": null,
"revenue": null
}
]
Rows are ordered by clicks DESC, then subId ASC for stable rendering. Clicks with a NULL sub_id are excluded. The source, medium, and campaign fields are taken from the most recent click's UTM tags for each sub-id.
bookings, conversionPercent, and revenue fields are intentionally null in this release. Booking attribution is tracked separately and will be wired into this endpoint in a follow-up — until then, treat them as not-yet-available rather than zero.400 Invalid date range (nested error shape)
{
"error": {
"message": "from and to query parameters are required"
}
}
404 Affiliate not found (nested error shape) — the {affiliateSlug} path segment did not match any affiliate.
500 Server error (nested error shape)
{
"error": {
"message": "Failed to get sub-id performance"
}
}
Server error — safe to retry with backoff.
Revenue Total
Get total revenue generated through the affiliate's referral link for a date range.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
from | Yes | Start date in YYYY-MM-DD format (inclusive) |
to | Yes | End date in YYYY-MM-DD format (inclusive). Must not be before from. |
Response
200 Success
{
"total": 45000
}
0 if no revenue data has been recorded for the period.400 Missing or invalid date range (flat error shape)
404 Affiliate not found (flat error shape) — the {affiliateSlug} path segment did not match any affiliate.
500 Server error (flat error shape)
{
"error": "Failed to get revenue"
}
Server error — safe to retry with backoff.
Expected Commission
Get expected commission for a date range.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
from | Yes | Start date in YYYY-MM-DD format (inclusive) |
to | Yes | End date in YYYY-MM-DD format (inclusive). Must not be before from. |
Response
200 Success
{
"total": 2250
}
0 if no commission data has been recorded for the period.400 Missing or invalid date range (flat error shape)
404 Affiliate not found (flat error shape) — the {affiliateSlug} path segment did not match any affiliate.
500 Server error (flat error shape)
{
"error": "Failed to get commission"
}
Server error — safe to retry with backoff.
Payout History
Get the full payout transaction history for an affiliate.
Response
200 Success
{
"history": [
{
"id": "f1e2d3c4-b5a6-7890-fedc-ba0987654321",
"affiliateId": "3f8a92b1-4e5c-4d2a-9f1b-8c7d6e5f4a3b",
"amount": 19800,
"status": "paid",
"requestDate": "2026-05-05",
"paidDate": "2026-05-10",
"months": [
{
"monthId": "2026-05",
"amount": 8000,
"reservations": [
{ "reservationCode": "9LTE18", "reservationId": "res-guid-1", "amount": 5000, "carenReservationId": 2065791, "verification": "verified" },
{ "reservationCode": "9LTE22", "reservationId": "res-guid-2", "amount": 3000, "carenReservationId": 2065844, "verification": "flagged", "verificationReason": "amount_mismatch", "expectedAmount": 2450 }
]
},
{ "monthId": "2026-04", "amount": 11800 }
],
"createdAt": "2026-05-05T10:00:00Z",
"updatedAt": "2026-05-10T14:00:00Z"
},
{
"id": "a9b8c7d6-e5f4-3210-abcd-ef9876543210",
"affiliateId": "3f8a92b1-4e5c-4d2a-9f1b-8c7d6e5f4a3b",
"amount": 500,
"status": "pending",
"requestDate": "2026-03-15",
"paidDate": null,
"months": [],
"createdAt": "2026-03-15T09:30:00Z",
"updatedAt": "2026-03-15T09:30:00Z"
}
]
}
months is always present in the response. Legacy payouts (submitted before the per-month breakdown shipped, or submitted via the scalar-amount body shape) return "months": []. Within a month, reservations is only echoed back when it was submitted — months submitted without a reservation itemization return no reservations key. createdAt is the ISO-8601 UTC timestamp when the row was inserted — use it to render "requested at" in your UI. Each reservation entry may also carry carenReservationId (numeric, read-only) — a field populated by Blue after the payout is accepted once the background enrichment process completes. Payouts queried immediately after creation may show reservations without this field; the field appears once enrichment finishes and can be observed in subsequent GET /payouts/history calls.
Each reservation entry may also carry verification, verificationReason, and expectedAmount — read-only fields populated by Blue's background verification process on the same asynchronous schedule as carenReservationId. verification is one of verified, flagged, or unverifiable, and is absent until Blue's verification for that reservation completes. verificationReason (one of amount_mismatch, not_found, rate_not_configured) is present only when verification is flagged or unverifiable. expectedAmount (the commission Blue computed from Caren booking data) is present only when verificationReason is amount_mismatch. All three fields are output-only; Blue verifies each reservation's claimed amount against Caren booking data independently of anything the partner submits.
Payout Status Values
| Status | Description |
|---|---|
pending | Request submitted, awaiting approval |
approved | Approved by administrator, awaiting payment |
paid | Payment completed |
rejected | Request rejected by administrator |
404 Affiliate not found (flat error shape) — the {affiliateSlug} path segment did not match any affiliate.
500 Server error (flat error shape)
{
"error": "Failed to get payout history"
}
Server error — safe to retry with backoff.
Submit Payout Request
Submit a payout request. Requests are accepted subject to payload validation below (no balance check is performed — the partner computes the affiliate's balance on their own side) and then verified: Blue's background process checks each reservation's claimed amount against Caren booking data, and a Blue admin approves the request before payment. Two request-body shapes are accepted — the per-month breakdown is preferred; the legacy scalar-amount shape is kept for older integrations.
Request Body — per-month breakdown (preferred)
{
"months": [
{
"monthId": "2026-05",
"amount": 8000,
"reservations": [
{ "reservationCode": "9LTE18", "reservationId": "res-guid-1", "amount": 5000 },
{ "reservationCode": "9LTE22", "reservationId": "res-guid-2", "amount": 3000 }
]
},
{ "monthId": "2026-04", "amount": 11800 }
],
"total": 19800
}
reservations is optional per month. A month can be submitted with only monthId and amount (as with the second month above), or itemized down to individual reservations. Mixing itemized and non-itemized months in the same request is allowed.
Request Body — legacy scalar amount
{
"amount": 500
}
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
months | array | preferred | Per-month breakdown of the payout. When present, total is required and amount is ignored. Persisted verbatim on the payout row for reporting. |
months[].monthId | string | yes (if months present) | ISO year-month identifier matching ^\d{4}-(0[1-9]|1[0-2])$ — for example 2026-05. 2026-5 and May-2026 are rejected. |
months[].amount | number | yes (if months present) | Must be strictly greater than zero. |
months[].reservations | array | optional | Per-reservation itemization of the month. When present, the reservation amounts must sum to the month's amount within the same 0.01 tolerance. Months without a reservations key remain valid. |
months[].reservations[].reservationCode | string | yes (if reservations present) | Caren reservation code (e.g. 9LTE18). Non-empty, at most 50 characters. |
months[].reservations[].reservationId | string | yes (if reservations present) | Caren reservation GUID, stored opaque. Non-empty, at most 100 characters. Must be unique within the request. A reservationId already claimed in a previous non-rejected payout is accepted but flagged for admin review. |
months[].reservations[].amount | number | yes (if reservations present) | Strictly greater than zero. |
months[].reservations[].carenReservationId | number | no (output only) | Numeric Caren reservation ID, populated by Blue after the payout is accepted. Read-only on output; any value sent in the request is ignored. Present only after Blue's background enrichment resolves the reservation against Caren. |
months[].reservations[].verification | string | no (output only) | One of verified, flagged, unverifiable. Read-only; populated by Blue's background verification process once it completes for that reservation. Absent until then. Any value sent in the request is ignored. |
months[].reservations[].verificationReason | string | no (output only) | One of amount_mismatch, not_found, rate_not_configured. Read-only; present only when verification is flagged or unverifiable. Any value sent in the request is ignored. |
months[].reservations[].expectedAmount | number | no (output only) | The commission Blue computed from Caren booking data for this reservation. Read-only; present only when verificationReason is amount_mismatch. Any value sent in the request is ignored. |
total | number | yes (if months present) | Strictly greater than zero. Must equal the sum of months[].amount within a tolerance of 0.01. Used as an integrity check on the submitted breakdown — the stored amount is the server-computed sum of the month amounts, so a partner cannot rely on a mismatched total to alter it. |
amount | number | only when months is absent | Legacy scalar amount. Strictly greater than zero. |
Validation rules
- When
monthsis present, everymonthIdmust match the format above and everyamountmust be positive. - Every
monthIdin a single request must be unique. totalmust equal the sum of month amounts (tolerance0.01).- When a month carries
reservations, everyreservationCodeandreservationIdmust be non-empty within their length caps, every reservationamountmust be strictly positive, and the reservation amounts must sum to the month'samountwithin a tolerance of0.01. - Every
reservationIdin a single request must be unique (across all months in the request). - A single request carries at most 1000 reservation entries in total across all months.
Response
201 Created
{
"id": "c3d4e5f6-a7b8-9012-cdef-ab3456789012",
"affiliateId": "3f8a92b1-4e5c-4d2a-9f1b-8c7d6e5f4a3b",
"amount": 19800,
"status": "pending",
"requestDate": "2026-05-31",
"paidDate": null,
"months": [
{
"monthId": "2026-05",
"amount": 8000,
"reservations": [
{ "reservationCode": "9LTE18", "reservationId": "res-guid-1", "amount": 5000 },
{ "reservationCode": "9LTE22", "reservationId": "res-guid-2", "amount": 3000 }
]
},
{ "monthId": "2026-04", "amount": 11800 }
],
"createdAt": "2026-05-31T12:00:00Z",
"updatedAt": "2026-05-31T12:00:00Z"
}
Legacy scalar-amount requests receive the same shape with "months": [].
Within each reservation entry, carenReservationId is read-only and populated by Blue after the payout is accepted. The creation response never includes it — enrichment runs asynchronously after the response is sent, so the field only appears on subsequent GET /payouts/history calls once Blue's background process resolves the reservation against Caren. Partners must not send carenReservationId in the request body; any value is silently ignored.
400 Validation error (flat error shape). The error field is one of:
Invalid request body— the JSON did not parse.invalid monthId format— one of the submittedmonthIdvalues did not matchYYYY-MM.month amount must be greater than zero— at least one month entry had a non-positiveamount.duplicate monthId— the samemonthIdappeared more than once inmonths.months breakdown exceeds 60 entries— too many rows in a single request. A payout can span at most 60 months.reservationCode is required and must be at most 50 characters— a reservation entry was missingreservationCodeor its value exceeded the length cap.reservationId is required and must be at most 100 characters— a reservation entry was missingreservationIdor its value exceeded the length cap.reservation amount must be greater than zero— at least one reservation entry had a non-positiveamount.duplicate reservationId in request— the samereservationIdappeared more than once in the request (uniqueness is enforced across all months in the same submission).reservations exceed 1000 entries— the total number of reservation entries across all months exceeded the per-request cap.reservation amounts for {monthId} do not sum to the month amount— a month's reservation amounts drifted from itsamountby≥ 0.01.total is required when months is provided— the request omitted thetotalfield entirely.total must be greater than zero—totalwas sent but was not strictly positive.total does not match sum of months— the drift betweentotaland the sum of month amounts was≥ 0.01.Amount must be greater than zero— the legacy scalar-body path received a non-positiveamount.
{
"error": "total does not match sum of months"
}
404 Affiliate not found (flat error shape) — the {affiliateSlug} path segment did not match any affiliate.
500 Server error (flat error shape)
{
"error": "Failed to create payout request"
}
Server error — safe to retry with backoff.
months breakdown is stored for reporting only — the admin approves and pays the scalar amount, which equals the server-side sum of the month amounts.amount against Caren booking data, populating verification (and, when applicable, verificationReason / expectedAmount) asynchronously — see the carenReservationId and verification field notes under Payout History. A flagged or unverifiable reservation does not block the payout; approval by a Blue admin remains the gate before payment.Get Bank Account
Get the affiliate's configured bank account. Returns the same shape that appears under affiliate.bankAccount in the login response, without the wrapper.
Response
200 Success
{
"holderName": "Demo Affiliate ehf.",
"bankName": "Landsbankinn",
"iban": "IS140159260076545510730339",
"swift": "NBIIISRE"
}
The swift field is null when no SWIFT/BIC code has been recorded.
404 Bank account not configured (nested error shape)
{
"error": {
"message": "Bank account not configured"
}
}
404 Affiliate not found (nested error shape)
{
"error": {
"message": "Affiliate not found"
}
}
500 Server error (nested error shape)
{
"error": {
"message": "Failed to get bank account"
}
}
Server error — safe to retry with backoff.
Update Bank Account
Create or replace the affiliate's bank-account details. The IBAN is persisted in canonical form (uppercased, internal spaces stripped).
Request Body
{
"holderName": "Demo Affiliate ehf.",
"bankName": "Landsbankinn",
"iban": "IS14 0159 2600 7654 5510 7303 39",
"swift": "NBIIISRE"
}
Parameters
| Field | Required | Description |
|---|---|---|
holderName | Yes | Account holder name. Non-empty after trimming. |
bankName | Yes | Bank name. Non-empty after trimming. |
iban | Yes | IBAN or Iceland domestic account number. See accepted formats below. |
swift | No | SWIFT/BIC code. May be omitted, null, or empty — all three normalise to NULL in storage. |
Accepted IBAN Formats
The iban field accepts any of the following (whitespace and case are normalised before matching):
| Format | Example | Description |
|---|---|---|
| Iceland domestic 4-2-6 | 0159-26-007654 | Four-digit bank code, two-digit branch, six-digit account. Dashes optional. |
| Iceland IBAN | IS140159260076545510730339 | IS followed by 24 digits (26 chars total). |
| Generic ISO 13616 IBAN | DE89370400440532013000 | Two-letter country code, two check digits, then 11–30 alphanumerics. Total length 15–34 chars. |
IS14 0159…); these are accepted and stored canonical (no spaces, uppercase).Response
200 Success — returns the saved bank account in canonical form.
{
"holderName": "Demo Affiliate ehf.",
"bankName": "Landsbankinn",
"iban": "IS140159260076545510730339",
"swift": "NBIIISRE"
}
400 Validation error (nested error shape)
The message identifies the offending field. Possible values:
| Message | Cause |
|---|---|
holderName is required | holderName missing or whitespace-only |
bankName is required | bankName missing or whitespace-only |
iban is required | iban missing or whitespace-only |
invalid IBAN format | iban does not match any accepted format |
Invalid request body | JSON parse error |
{
"error": {
"message": "invalid IBAN format"
}
}
404 Affiliate not found (nested error shape)
{
"error": {
"message": "Affiliate not found"
}
}
500 Server error (nested error shape)
{
"error": {
"message": "Failed to update bank account"
}
}
Server error — safe to retry with backoff.
List Notifications
Get the affiliate's most recent notifications, ordered by creation time descending. Returns up to 100 records.
Response
200 Success — always an array, never null.
[
{
"id": "9c1d2e3f-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"title": "Payout approved",
"body": "Your payout request of 500 ISK has been approved.",
"createdAt": "2026-04-30T12:00:00Z",
"read": false
}
]
The read field is a real boolean — it becomes true once the notification's read_at timestamp is stamped. createdAt is RFC3339 UTC.
404 Affiliate not found (nested error shape) — the {affiliateSlug} slug did not match any affiliate.
500 Server error (nested error shape)
{
"error": {
"message": "Failed to list notifications"
}
}
Server error — safe to retry with backoff.
Mark All Notifications Read
Stamp read_at on every unread notification for the affiliate. No request body required.
Response
200 Success — returns the count of rows updated.
{
"updated": 3
}
updated is a non-negative integer (int64).
{"updated": 0} with the same 200 status.404 Affiliate not found (nested error shape) — the {affiliateSlug} slug did not match any affiliate.
500 Server error (nested error shape)
{
"error": {
"message": "Failed to mark notifications read"
}
}
Server error — safe to retry with backoff.
Mark Notification Read
Stamp read_at on a single notification. No request body required.
Path Parameters
| Parameter | Description |
|---|---|
affiliateSlug | The affiliate's slug (from the login response). URL-safe identifier. |
notificationId | The notification's UUID (from the list endpoint). Notification IDs are UUIDs, not slugs. |
Response
200 Success
{
"status": "ok"
}
400 Validation error (nested error shape)
The message identifies the offending field. Possible values:
| Message | Cause |
|---|---|
notification ID is required | notificationId path segment missing or empty |
invalid notification ID format | notificationId is not a valid UUID |
{
"error": {
"message": "invalid notification ID format"
}
}
404 Notification not found (nested error shape)
{
"error": {
"message": "Notification not found"
}
}
404 Affiliate not found (nested error shape) — the {affiliateSlug} path segment did not match any affiliate.
500 Server error (nested error shape)
{
"error": {
"message": "Failed to mark notification read"
}
}
Server error — safe to retry with backoff.
General Notes
| Topic | Detail |
|---|---|
| Date format | Query parameters (from, to) are always YYYY-MM-DD. Response timestamps are RFC3339 UTC (YYYY-MM-DDTHH:MM:SSZ). The date field in clicksPerDay is a calendar day (YYYY-MM-DD), not a timestamp. |
| Currency | All monetary amounts are in ISK (Icelandic Króna). Monetary fields are JSON numbers (not strings). Whole-króna amounts serialize without a decimal point (e.g. 2845, not 2845.00); fractional amounts include a decimal point as needed. |
| Affiliate ID | Affiliate slug. URL-safe, stable, returned by POST /auth/login in the slug field. Include in all subsequent requests. The id UUID in the login response is internal and is not used in URLs. |
| Content-Type | All requests and responses use application/json |
| Empty arrays | List endpoints — including notifications and sub-id performance — return [] (not null) when no records exist |
| Error shapes | Two shapes coexist: flat ({"error": "..."}) for endpoints predating the affiliate portal, and nested ({"error": {"message": "..."}}) for endpoints added 2026-04 onwards. See the Error Handling section above. |
© 2026 Blue Car Rental — Blue's Affiliate API
Questions? Contact gudmundur@bluecarrental.is