
# Create an Admin Portal link

`POST /v1/admin-portal-links` · permission `admin-portal-links:write`

Creates a link that lets the IT administrator of one of your customer organizations set up SSO, directory sync, domain verification and more in the hosted Admin Portal, without your support team. Generate a fresh link each time your administrator clicks "Set up SSO" in your application and redirect them to `url` right away: the link is valid for 5 minutes and opens one portal session exactly once. `url` is returned only in this response, once; no endpoint returns it later. The response carries `Cache-Control: no-store`, and a response replayed for the same `Idempotency-Key` omits `url` and carries `secret_redacted: true`. The portal session lasts 60 minutes idle, 8 hours at most; revoke it with `DELETE /v1/admin-portal-links/{link_id}`. The creation is audited as `admin_portal_link.created` (the URL never enters the audit log).

`audit_logs` needs the Business plan and `log_streams` the Enterprise plan; the other intents are available on every plan. `delivery: email` sends an invitation instead: it lives 7 days and is mailed to `recipient_email`; the response carries no `url` and no address. When the recipient opens it, a 6-digit code (valid 10 minutes) goes to the same address, and only the verified code opens the portal session. If the invitation mail cannot be sent, the call answers `503 service_unavailable` with `Retry-After` and creates nothing. Revoke an invitation with `DELETE /v1/admin-portal-links/{link_id}`. The recipient address never appears in a response, a list, the audit log or a log.

## Request

| Field | Type | Required | Description |
|---|---|---|---|
| `organization_id` | string | yes | Organization ID (`org_…`); it must exist in this environment. |
| `intents` | string[] | yes | 1–6 of `sso`, `dsync`, `domain_verification`, `certificate_renewal`, `audit_logs` (Business), `log_streams` (Enterprise), none twice. |
| `return_url` | string | yes | Absolute `https` URL of at most 2048 bytes, without user information or fragment. Its origin must be the origin of a `redirect_uri` of an active application registered in this environment. |
| `delivery` | string | no | `link` (default; 5-minute one-time `url` in the response) or `email` (7-day invitation mailed to `recipient_email`; no `url`). |
| `recipient_email` | string | no | Only with `delivery: email`, and required there; at most 320 characters. Missing: `422` `/recipient_email` `required`; with `delivery: link`: `not_allowed`; not a valid address: `invalid_value`. |

## Response

`201 Created` with the link and its one-time `url`.

```json
{
  "id": "apl_01j9xv2c4d6e8f0g2h4j6k8m0n",
  "organization_id": "org_01j9xk3m7q8r2s4t6v8w0y2z4a",
  "intents": [
    "dsync",
    "sso"
  ],
  "delivery": "link",
  "return_url": "https://app.example.com/settings/sso",
  "expires_at": "2026-10-12T09:36:00.123456Z",
  "consumed_at": null,
  "revoked_at": null,
  "created_at": "2026-10-12T09:31:00.123456Z",
  "url": "https://acme.eu.keenauth.app/portal/l/<token>"
}
```

## Errors

| HTTP | code | When |
|---|---|---|
| 403 | `plan_feature_unavailable` | An intent needs a higher plan; `feature` and `required_plan` name it. |
| 404 | `not_found` | The organization does not exist in this environment. |
| 409 | `organization_disabled` | The organization is not active (disabled); no link is created. |
| 409 | `organization_pending_deletion` | The organization is scheduled for deletion; no link is created. |
| 422 | `validation_failed` | A field is missing or invalid; `/return_url` `origin_not_registered` when its origin is not a registered application origin; `/recipient_email` `required`, `not_allowed` or `invalid_value`. |
| 401 | `unauthenticated` | The credential is missing, invalid or revoked. |
| 403 | `insufficient_permissions` | The credential lacks the required permission. |
| 429 | `rate_limited` | The rate limit was exceeded; retry after the `Retry-After` period. |
| 503 | `service_unavailable` | A dependency is temporarily unavailable, or (`delivery: email`) the invitation mail cannot be sent; nothing is created. Retry after the `Retry-After` period. |

## Example

```bash
curl -i -X POST "https://api.eu.keenauth.com/v1/admin-portal-links" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"organization_id":"org_01j9xk3m7q8r2s4t6v8w0y2z4a","intents":["sso","dsync"],"return_url":"https://app.example.com/settings/sso"}'
```

### Send an invitation by e-mail

```bash
curl -i -X POST "https://api.eu.keenauth.com/v1/admin-portal-links" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"organization_id":"org_01j9xk3m7q8r2s4t6v8w0y2z4a","intents":["sso"],"return_url":"https://app.example.com/settings/sso","delivery":"email","recipient_email":"it@customer.example"}'
```

`201 Created`; there is no `url` and no address in the response:

```json
{
  "id": "apl_01j9xv2c4d6e8f0g2h4j6k8m0n",
  "organization_id": "org_01j9xk3m7q8r2s4t6v8w0y2z4a",
  "intents": [
    "sso"
  ],
  "delivery": "email",
  "return_url": "https://app.example.com/settings/sso",
  "expires_at": "2026-10-19T09:31:00.123456Z",
  "consumed_at": null,
  "revoked_at": null,
  "created_at": "2026-10-12T09:31:00.123456Z"
}
```

With the CLI: `keenauth admin-portal-links create --organization-id org_01j9xk3m7q8r2s4t6v8w0y2z4a --intents sso --return-url https://app.example.com/settings/sso --delivery email --recipient-email it@customer.example`.
