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.