Create invitations in bulk
POST /v1/invitations:batch · permission invitations:write
Creates 1 to 100 invitations (items). Each item follows the rules of POST /v1/invitations and is written on its own: a failing item does not stop the others. The response is 200 OK and items keeps the request order; an item's status is 201 (new invitation), 200 (a pending invitation was resent) or the HTTP status of its error, and a failed item carries error with code, status and field pointers of the form /items/<i>/…. The accept_url of link_only items is returned only in this response (Cache-Control: no-store); a response replayed with Idempotency-Key has no links and returns secret_redacted: true. An item without organization_id is an environment invitation. An email item is sent by e-mail; if invitation e-mail is not configured for the deployment it is a 501 not_implemented item error, and an application_id that does not exist in this environment is a 404 not_found item error. Each written invitation is audited as invitation.created.
Request
| Field | Type | Required | Description |
|---|---|---|---|
items |
object[] | yes | 1 to 100 invitations, each with the fields of POST /v1/invitations. |
Idempotency-Key |
header | no | Makes a retry return the stored result without links. |
Response
200 OK with one result per item.
{
"items": [
{
"status": 201,
"invitation": {
"id": "inv_01j9xt9v3w4y8z0a2b4c6d8e0g",
"organization_id": "org_01j9xk3m7q8r2s4t6v8w0y2z4a",
"email": "ada@example.com",
"role_ids": [
"rol_01j9xr7s1t2w6x8y0z2a4b6c8e"
],
"application_id": null,
"delivery": "link_only",
"delivery_status": "not_sent",
"status": "pending",
"send_count": 1,
"last_sent_at": "2026-10-12T09:31:00.123456Z",
"expires_at": "2026-10-19T09:31:00.123456Z",
"accepted_user_id": null,
"accepted_at": null,
"revoked_at": null,
"created_at": "2026-10-12T09:31:00.123456Z",
"updated_at": "2026-10-12T09:31:00.123456Z",
"version": 1,
"accept_url": "https://acme.eu.keenauth.app/u/login?invitation=<token>"
}
},
{
"status": 422,
"error": {
"code": "validation_failed",
"status": 422,
"errors": [
{
"pointer": "/items/1/email",
"code": "invalid_value"
}
]
}
}
]
}
Errors
| HTTP | code | When |
|---|---|---|
| 422 | validation_failed |
items is missing, empty or longer than 100. |
| 401 | unauthenticated |
The credential is missing, invalid or revoked. |
| 403 | insufficient_permissions |
The credential lacks the required permission. An item with a privileged (is_privileged) role fails with this item error when the credential lacks roles:write. |
| 429 | rate_limited |
The rate limit was exceeded; retry after the Retry-After period. |
| 503 | service_unavailable |
A dependency is temporarily unavailable; retry after the Retry-After period. |
Example
curl -i -X POST "https://api.eu.keenauth.com/v1/invitations:batch" \
-H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0f6c1d2e3a4b5c6d7e8f9a0b" \
-d '{"items":[{"organization_id":"org_01j9xk3m7q8r2s4t6v8w0y2z4a","email":"ada@example.com","delivery":"link_only"},{"organization_id":"org_01j9xk3m7q8r2s4t6v8w0y2z4a","email":"bob@example.com","delivery":"link_only"}]}'
Personal data
This endpoint carries personal data: organization memberships and invitations. Personal data in the KeenAuth APIs describes what is stored, how long it is kept and how it is deleted. Do not send special categories of personal data (health, biometric data, religion, political opinions, sexual orientation) in any field.