
# Add an IdP signing certificate to a connection

`POST /v1/connections/{connection_id}/certificates` · permission `connections:write`

Adds a signing certificate of the identity provider to an enterprise SAML connection. Use it to upload the certificate by hand or to rotate certificates: add the new certificate first, switch the identity provider over, then [delete](delete.md) the old one. Rotation takes effect immediately, also on an active connection.

The certificate is one PEM `CERTIFICATE` block (at most 8 KiB). It must not be expired and must have an RSA key of at least 2048 bits or an ECDSA P-256, P-384 or P-521 key. When it is rejected the response does not say why, and neither the reason nor the certificate content is logged. A connection has at most 3 active certificates. A certificate that was deleted from the connection cannot be added again. The request takes no `If-Match` header. Writes are audited as `connection.idp_certificate_added`.

## Request

| Field | Type | Required | Description |
|---|---|---|---|
| `connection_id` | path | yes | Enterprise SAML connection ID (`conn_…`). |
| `certificate` | string | yes | One PEM certificate (`-----BEGIN CERTIFICATE-----` …), at most 8 KiB. |

## Response

`201 Created` with the new certificate (see [List certificates](list.md)). When the same certificate is already on the connection the response is `200 OK` with the registered certificate; nothing is written or audited.

```json
{"id": "conncert_01j9xq2m4n6p8r0s2t4v6w8y0a", "sha256_fingerprint": "3b0c…", "not_before": "2026-01-01T00:00:00Z", "not_after": "2028-01-01T00:00:00Z", "source": "manual"}
```

## Errors

| HTTP | code | When |
|---|---|---|
| 404 | `not_found` | The connection does not exist in this environment or is not an enterprise connection. |
| 409 | `conflict` | The connection is not a SAML connection, has no SAML configuration yet, already has 3 active certificates, or the certificate was deleted from it before. |
| 422 | `validation_failed` | The certificate is missing, too large or not accepted; `errors[]` points at `/certificate`. Staging and production environments also refuse a certificate over the public test key of the [local mock server](/docs/cli/mock-server). |
| 400 | `unknown_field` | The body has an unknown field. |
| 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; retry after the `Retry-After` period. |

## Example

```bash
jq -Rs '{certificate: .}' idp-signing.pem > cert.json
curl -X POST "https://api.eu.keenauth.com/v1/connections/conn_01j9xp6q0r1s5t7v9w1x3y5z7b/certificates" \
  -H "Authorization: Bearer $KEENAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d @cert.json
```

```bash
keenauth connections certificates add conn_01j9xp6q0r1s5t7v9w1x3y5z7b --certificate-file idp-signing.pem
```
