Issue up to 100 Kicbak invites in a single request, each funded by your organisation's wallet with a shared reward rule.
Exchange your organisation's client key and secret for a bearer token, then send Authorization: Bearer <token>. Organisation clients only.
| Field | Type | Required | Description |
|---|---|---|---|
| Content-Type | string | required | Must be application/json. |
| Authorization | string | required | Bearer token obtained from POST /api/third-party/v1/auth/token, e.g. "Bearer eyJhbGci...". |
| x-client-key | string | deprecated | Deprecated — use Authorization instead. Still accepted so existing integrations keep working; responses carry Deprecation: true. |
| x-client-secret | string | deprecated | Deprecated — sent alongside x-client-key on the legacy path. |
| Field | Type | Required | Description |
|---|---|---|---|
| invites | array<InviteItem> | required | 1 to 100 invites. Each is { email, name?, reference? }. |
| rewardSpecKind | "FIXED_KIC" | "FIXED_USD" | "PERCENT_OF_USD" | required | How the invite reward is calculated. |
| rewardKicAmount | integer | conditional | Required for FIXED_KIC. Whole $kic, max 1,000,000. |
| rewardUsd | number | conditional | Required for FIXED_USD. Max $10,000. |
| rewardPercent | number | conditional | Required for PERCENT_OF_USD. Max 100. |
| basisUsd | number | conditional | Required for PERCENT_OF_USD. Max $100,000. |
| grantTrigger | "ON_JOIN" | "ON_QUALIFYING_EVENT" | optional | Defaults to ON_JOIN. |
| expiresAt | string (ISO 8601) | optional | Must be a date in the future. |
| requestId | string | required | A unique string identifying this batch, 8–200 characters. Send the same request again — same requestId and emails — and the original invites come back with replay: true and a 200, instead of a second set. Reusing a requestId with a DIFFERENT email set is refused with 409. |
InviteItem (each entry in invites)
| Field | Type | Required | Description |
|---|---|---|---|
| string | required | A valid email address. Deduplicated case-insensitively. | |
| name | string | optional | Recipient's display name, max 120 characters. |
| reference | string | optional | Your own identifier for THIS invitation, 1–200 characters. It comes back on every response and is how you look this invitation up later. Distinct from requestId, which names the batch. Each reference may be used once. |
If a request times out you cannot tell whether the invites were created. Send it again with the same requestId and you get the original invites back rather than a duplicate set. It is required because a claimed invite fixes who referred that traveller permanently, so a duplicate that gets claimed cannot be corrected afterwards.
A batch sent with a sandbox credential creates the invitations in the sandbox database and returns their codes exactly as production does, but no invitation email is delivered to anyone.
curl -X POST https://kicbak.co/api/third-party/v1/kicbak/invites/bulk \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $KICBAK_TOKEN" \
-d '{
"invites": [
{ "email": "jane@example.com", "name": "Jane Doe", "reference": "booking-88213" },
{ "email": "john@example.com", "reference": "booking-88214" }
],
"rewardSpecKind": "FIXED_KIC",
"rewardKicAmount": 500,
"requestId": "invite-batch-2026-08-04-0001"
}'Every response is JSON with a status field (1 success, 0 failure). Failures also carry a code your error handler can branch on — it never changes if we reword message.
{
"status": 1,
"message": "2 invites created successfully.",
"data": {
"created": [
{ "email": "jane@example.com", "code": "K7XQ2M9P", "shareUrl": "https://kicbak.co/join?invite=K7XQ2M9P", "reference": "booking-88213" },
{ "email": "john@example.com", "code": "R4T8W1YZ", "shareUrl": "https://kicbak.co/join?invite=R4T8W1YZ", "reference": "booking-88214" }
]
}
}| Status | Code | Meaning |
|---|---|---|
| 200 | — | Replay: this exact requestId + emails was already created. The originals return with replay: true; nothing is created again. |
| 400 | — | Malformed JSON, failed validation, missing wallet/price, or reward over the max. |
| 401 | UNAUTHORIZED | No readable credential, or a token that doesn't decode/validate. |
| 401 | TOKEN_EXPIRED | Token aged out. Re-exchange the same client_key/client_secret and retry. |
| 401 | CREDENTIAL_ROTATED | This token was minted under a secret that has since been rotated. Exchange your new secret. |
| 401 | CREDENTIAL_EXPIRED | The credential itself is past its own expiry — re-exchanging will not help; request a new credential. |
| 401 | CLIENT_INACTIVE | The client has been deactivated. |
| 403 | NOT_ORGANIZATION | Client is not organisation-owned — only organisation clients can call this endpoint. |
| 503 | SANDBOX_UNAVAILABLE | You are using a sandbox credential and the sandbox environment is temporarily unavailable on our side. Retry shortly; production credentials are unaffected. |
| 403 | — | The client's organisation exists but is not yet ACTIVE. |
| 404 | — | The organisation tied to this client no longer exists. |
| 400 | REFERENCE_DUPLICATED_IN_REQUEST | Two invites in the same request carry the same reference. |
| 409 | REFERENCE_ALREADY_USED | A reference in this request is already used by one of your invitations; the errors array names the holder. |
| 409 | — | One or more emails are already registered users or have an active pending invite; OR this requestId was already used for a different set of emails. |
| 429 | — | More than 20 requests from this client within the last hour. |
| 500 | — | Unexpected server error. Safe to retry. |
20 requests per hour per client. Emails are deduplicated and checked against existing users and active invites before anything is created — if any email is ineligible, the whole request is rejected with 409 and nothing is created.