{
  "schema_version": 2,
  "id": "operate/iris/identity-service/operations",
  "title": "Operate the Identity Service",
  "url": "https://redis.io/docs/latest/operate/iris/identity-service/operations/",
  "summary": "Mint, list, rotate, and revoke agent keys, rotate credentials, audit changes, upgrade, back up, and troubleshoot the Identity Service.",
  "tags": [
    "docs",
    "operate",
    "iris"
  ],
  "last_updated": "2026-11-03T13:28:05-08:00",
  "page_type": "content",
  "content_hash": "6d1ed82b039d39f40355a1bf23e03592ff4be2e2326148d428e357593c17fe1b",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "This page covers the daily work on an Identity Service release: managing agent keys for every service, rotating credentials, and finding who changed what. It also covers upgrades, backups of the metadata Redis, uninstalling, and troubleshooting.\n\nThe examples use release `ids` and a port-forward to its Service:\n\n[code example]\n\n[code example]\n\nWhen operator sign-in is on, use your own access token instead of the control token, so the audit log names you. See [Operator sign-in](https://redis.io/docs/latest/operate/iris/identity-service/authentication#operator-sign-in-with-your-identity-provider)."
    },
    {
      "id": "check-health",
      "title": "Check health",
      "role": "content",
      "text": "The health routes need no credential:\n\n[code example]\n\nReadiness returns `503` while the Identity Service can't reach the metadata Redis. `helm test ids -n <namespace-name>` runs the same check in the cluster."
    },
    {
      "id": "manage-agent-keys",
      "title": "Manage agent keys",
      "role": "content",
      "text": "The control API manages the keys of every service. The `iris` command-line interface (CLI) reads `IRIS_IDENTITY_URL` and `IRIS_IDENTITY_TOKEN`. To set it up, see [Set up on a self-managed deployment](https://redis.io/docs/latest/develop/ai/context-engine/sdk-and-cli#set-up-on-a-self-managed-deployment)."
    },
    {
      "id": "mint-an-agent-key",
      "title": "Mint an agent key",
      "role": "content",
      "text": "Find the resource ID in the service's control plane first, for example an Agent Memory store ID. Then mint the key:\n\n[code example]\n\nThe response returns `keyId` and `token`. The `token` is the agent key, and it's shown only once: give it to the agent through your secret store, and record only the `keyId`.\n\nOptional fields:\n\n- `expiresAt`: when the key stops working, in Unix seconds. Omitted or `0` means never.\n- `userId`: the user that owns the key. It needs `apiKeys.userBinding.enabled: true`.\n\nFor the grant of each service, see [Agent keys and grants](https://redis.io/docs/latest/operate/iris/identity-service/authentication#agent-keys-and-grants). With the CLI, each service's own command mints its keys, because it knows the grant the service needs. For example, `iris intent-router service key` mints a key for a Router.\n\nBefore the Identity Service stores the key, it asks the service's control plane to validate the grant. A `400` means the resource doesn't exist in the tenant or an action isn't valid. A `424` means the Identity Service can't reach the control plane or its internal token is wrong."
    },
    {
      "id": "list-and-inspect-keys",
      "title": "List and inspect keys",
      "role": "content",
      "text": "[code example]\n\nWith the CLI:\n\n[code example]\n\n`iris identity keys list` also filters by `--resource-type`. Neither the API nor the CLI ever returns a key's token after it's minted.\n\nThe list is paginated. Over HTTP, pass `maxResults` and then each response's `nextToken`. The CLI takes `--limit`, and `--cursor` with the `nextCursor` value it prints; `--all` reads every page."
    },
    {
      "id": "update-rotate-or-revoke-a-key",
      "title": "Update, rotate, or revoke a key",
      "role": "content",
      "text": "[code example]\n\n[code example]\n\n[code example]\n\nWith the CLI:\n\n[code example]\n\n`rotate` writes the new key to the dotenv file with mode `0600` and never prints it. `revoke` asks for confirmation unless you pass `--yes`, and `--product <product>` refuses keys that grant another service.\n\n| Operation | Use it when | What happens to the token | When the agent notices |\n| --- | --- | --- | --- |\n| Update | The agent needs other resources or actions, a new name, or a new expiry. | It doesn't change. | After the data plane's cache refreshes, within `runtime.cache.hardTtlSeconds` (300 seconds by default). |\n| Rotate | Someone else might know the token, or your rotation schedule is due. | You get a new `keyId` and token with the same grants. The old token works for `graceSeconds` more (default 3600, at most `apiKeys.maxRotateGraceSeconds`), but never after its own `expiresAt`. The response's `oldExpiresAt` says when it stops. | The agent must switch to the new token before `oldExpiresAt`. |\n| Revoke | Someone else has the token, or the agent no longer needs access. | The key is deleted. A second revoke returns `404`. | Within `runtime.cache.hardTtlSeconds`. |"
    },
    {
      "id": "rotate-credentials",
      "title": "Rotate credentials",
      "role": "content",
      "text": "The Identity Service reads the control token, the runtime credentials, and the internal tokens from their mounted files on each request. A changed Secret takes effect after Kubernetes updates the mounted file, in about a minute, without a restart. While the files update, some calls can fail.\n\n| Credential | How to rotate |\n| --- | --- |\n| Control token | Put a new value in your Secret. For a generated token, delete its Secret and run `helm upgrade`. |\n| Runtime credential | Update the Secret that the Identity Service and the data plane share, then change `existingSecretChecksum` on both releases and run `helm upgrade` on both. To avoid failed calls, follow [Rotate a runtime credential without failed calls](#rotate-a-runtime-credential-without-failed-calls). |\n| Internal token | Update the Secret that the Identity Service and the control plane share, then change `productValidation.products.<product>.credential.existingSecretChecksum` and the service's matching checksum, and run `helm upgrade` on both. |\n| Policy overlays | The Identity Service reads them only at startup. Change `secrets.checksum` and run `helm upgrade`. |\n| Metadata Redis credential | Follow [Rotate the metadata Redis credential](#rotate-the-metadata-redis-credential). |\n| Agent key | [Rotate it](#update-rotate-or-revoke-a-key) through the control API. |\n\nMake every new value unique. The Identity Service doesn't start when two credentials have the same value."
    },
    {
      "id": "rotate-a-runtime-credential-without-failed-calls",
      "title": "Rotate a runtime credential without failed calls",
      "role": "content",
      "text": "The Identity Service accepts more than one entry for the same `subject`, so it can accept the old and the new value for a while. This example rotates the Agent Memory credential:\n\n1. Create a Secret with a new value:\n\n   [code example]\n\n1. Add a second entry with the same `subject` and a new `name`, and keep the old entry:\n\n   [code example]\n\n1. Run `helm upgrade` for the Identity Service release.\n1. Point the data plane at the new Secret. For Agent Memory, set `identityService.credential.existingSecret: memory-dp-credential-v2` and run `helm upgrade` for Agent Memory.\n1. Wait until every data-plane pod uses the new Secret, and check that agent calls work.\n1. Remove the old entry from `values-ids.yaml` and run `helm upgrade` for the Identity Service release.\n1. Delete the `memory-dp-credential` Secret."
    },
    {
      "id": "find-who-did-what",
      "title": "Find who did what",
      "role": "content",
      "text": "The Identity Service writes one log line with the message `audit event` for each control operation. It has no audit database, so collect the pod logs with your log system. To search the logs Kubernetes still keeps:\n\n[code example]\n\n| Field | Meaning |\n| --- | --- |\n| `action` | The operation, for example `api_key.create`, `api_key.revoke`, or `subject_grant.patch`. |\n| `result` | `allowed`, `denied`, or `failed`. |\n| `subject`, `user_id`, `issuer` | The caller. |\n| `auth_type` | `admin_token` for the control token. |\n| `authorized_by` | The role that allowed the operation, for example `suite_admin`. |\n| `key_id`, `target_subject` | The key or subject that the operation changed. |\n\nThe log never contains tokens. A request rejected before it reaches the service, such as one with a malformed body, has no audit line."
    },
    {
      "id": "upgrade",
      "title": "Upgrade",
      "role": "content",
      "text": "1. Read the release notes for the chart version.\n1. Upgrade each service's control plane first. A newer Identity Service can send grant fields that an older control plane doesn't know.\n1. Upgrade the Identity Service release with the same values file:\n\n   [code example]\n\n1. Run `helm test ids -n <namespace-name>`, then mint and revoke a test key for each service.\n\n`helm rollback` restores the workload and its values. It doesn't change generated Secrets or the keys and grants in the metadata Redis."
    },
    {
      "id": "back-up-and-restore",
      "title": "Back up and restore",
      "role": "content",
      "text": "The metadata Redis holds every agent key, grant, and subject grant. Back up database 0 of that Redis on a schedule, with persistence turned on. Also export the Secrets you created for the release, such as the control token, the runtime credentials, and the internal tokens, and store the export as you store database passwords.\n\nA restore brings back the keys and grants as they were at backup time:\n\n- Keys minted after the backup don't work. Mint them again and give them to their agents.\n- Keys revoked after the backup work again. Revoke them again."
    },
    {
      "id": "move-the-metadata-redis",
      "title": "Move the metadata Redis",
      "role": "content",
      "text": "Stop the Identity Service during the move. If it keeps running after the copy, it changes only the old Redis.\n\n1. Set `replicaCount` to `0` and run `helm upgrade`.\n1. Copy the data to the new Redis.\n1. Put the new host in `metadata.urls` in the metadata Secret.\n1. Set `replicaCount` to its earlier value and run `helm upgrade`."
    },
    {
      "id": "rotate-the-metadata-redis-credential",
      "title": "Rotate the metadata Redis credential",
      "role": "content",
      "text": "The Identity Service reads the metadata Redis connection only at startup.\n\n1. In Redis, add the new user or password, and keep the old one valid.\n1. Update the metadata Secret, and every service Secret that uses the same Redis.\n1. Restart the Identity Service, and wait until it's ready:\n\n   [code example]\n\n1. Restart the service control planes, then their data planes and workers, if they use the same Redis.\n1. Run `helm test ids -n <namespace-name>`.\n1. Remove the old credential from Redis."
    },
    {
      "id": "planned-outage-of-the-metadata-redis",
      "title": "Planned outage of the metadata Redis",
      "role": "content",
      "text": "While the metadata Redis is down, the Identity Service turns unready, and no agent key can be checked, minted, or changed. Kubernetes doesn't restart the pod. When Redis returns, readiness recovers without a restart."
    },
    {
      "id": "uninstall",
      "title": "Uninstall",
      "role": "content",
      "text": "[code example]\n\nUninstalling keeps the Secrets that the chart generated, such as the control token, and keeps every key in the metadata Redis. Services can't check agent keys while the Identity Service is gone. To remove everything, delete the generated Secrets, the Secrets you created, and the metadata Redis data."
    },
    {
      "id": "troubleshooting",
      "title": "Troubleshooting",
      "role": "errors",
      "text": "The Identity Service returns errors as RFC 7807 problem documents. The `type` field names the error.\n\n| Symptom | Possible cause | What to do |\n| --- | --- | --- |\n| Readiness returns `503` | The Identity Service can't reach the metadata Redis. | Check the URL, user, password, and Transport Layer Security (TLS) settings in the metadata Secret. |\n| The pod doesn't start | Two credentials share a value, or a value isn't valid. | Read the first error line in the pod log. It names the field. |\n| `401` on the control API | Wrong control token, or the operator token has the wrong issuer, audience, or signature, or expired. | Compare the token's `iss` and `aud` claims with `controlAuth.oidc`. |\n| `403` on the control API | The caller has no role for this operation or service. | Check `controlAuth.roleBindings` and the groups in the token. |\n| `503` on the control API | The Identity Service can't read its token file or reach the JSON Web Key Set (JWKS) URI. | Check the Secret mount and the network path to your identity provider (IdP). |\n| `404` for a key that exists | The key is in another tenant. | Check the caller's tenant. |\n| `400` that names the tenant | The grant names a tenant other than `singleTenantID`. | Omit `tenant`, or set it to `singleTenantID`. |\n| `400` from grant validation | The resource doesn't exist in the tenant, or an action isn't valid. | Check the resource ID in the service's control plane, and that the service's `tenantId` equals `singleTenantID`. |\n| `424` when minting | The Identity Service can't call the validator: the control plane is down, the address is wrong, or the internal token doesn't match. | Check `productValidation.products.<product>` and the control-plane logs. |\n| Agents get `403` with a valid key | The key's tenant isn't the service's tenant, or the key has no grant for the resource or action. | Compare `singleTenantID` with the service's `tenantId`, and check the key's grants. |\n| Agents get `503`, and the data-plane log shows `service-credential-rejected` | The data plane's runtime credential doesn't match the Identity Service entry. | Compare the data plane's Secret with the Identity Service entry's Secret. |\n| Agents get `503`, and the data-plane log shows `service-credential-insufficient-permissions` | The runtime credential lacks the operation or service. | Add it to the entry's `allowedOperations` or `allowedProducts`. |\n| A user with a subject grant gets `403` | The issuer or subject differs from the token, or the shape isn't in `subjectGrants.allowed`. | Compare the token's `iss` and `sub` claims with the grant. |\n| A revoked key still works | The data plane's key cache. | Wait up to `runtime.cache.hardTtlSeconds`. |"
    }
  ],
  "examples": [
    {
      "id": "overview-ex0",
      "language": "bash",
      "code": "kubectl -n <namespace-name> port-forward svc/ids-iris-identity-service 9200:9200",
      "section_id": "overview"
    },
    {
      "id": "overview-ex1",
      "language": "bash",
      "code": "export IRIS_IDENTITY_URL='http://localhost:9200'\nexport IRIS_IDENTITY_TOKEN='<control-token-or-operator-access-token>'",
      "section_id": "overview"
    },
    {
      "id": "check-health-ex0",
      "language": "bash",
      "code": "curl -sS \"$IRIS_IDENTITY_URL/health/readiness\"",
      "section_id": "check-health"
    },
    {
      "id": "mint-an-agent-key-ex0",
      "language": "bash",
      "code": "curl -sS -X POST \"$IRIS_IDENTITY_URL/v1/api-keys\" \\\n  -H \"Authorization: Bearer $IRIS_IDENTITY_TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"name\": \"support-agent\",\n    \"grants\": [{\n      \"tenant\": \"default\",\n      \"product\": \"memory\",\n      \"resourceType\": \"mem-store\",\n      \"resourceId\": \"<store-id>\",\n      \"actions\": [\"read\", \"write\"]\n    }]\n  }'",
      "section_id": "mint-an-agent-key"
    },
    {
      "id": "list-and-inspect-keys-ex0",
      "language": "bash",
      "code": "curl -sS -H \"Authorization: Bearer $IRIS_IDENTITY_TOKEN\" \\\n  \"$IRIS_IDENTITY_URL/v1/api-keys?product=memory&resourceId=<store-id>\"\ncurl -sS -H \"Authorization: Bearer $IRIS_IDENTITY_TOKEN\" \\\n  \"$IRIS_IDENTITY_URL/v1/api-keys/<key-id>\"",
      "section_id": "list-and-inspect-keys"
    },
    {
      "id": "list-and-inspect-keys-ex1",
      "language": "bash",
      "code": "iris identity keys list --product memory --resource-id <store-id>\niris identity keys show <key-id>",
      "section_id": "list-and-inspect-keys"
    },
    {
      "id": "update-rotate-or-revoke-a-key-ex0",
      "language": "bash",
      "code": "# Change the grants, the name, or expiresAt. The token stays the same.\ncurl -sS -X PATCH \"$IRIS_IDENTITY_URL/v1/api-keys/<key-id>\" \\\n  -H \"Authorization: Bearer $IRIS_IDENTITY_TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"grants\": [{\"product\": \"memory\", \"resourceType\": \"mem-store\", \"resourceId\": \"<store-id>\", \"actions\": [\"read\"]}]}'",
      "section_id": "update-rotate-or-revoke-a-key"
    },
    {
      "id": "update-rotate-or-revoke-a-key-ex1",
      "language": "bash",
      "code": "# Rotate: get a new key; the old one keeps working for graceSeconds.\ncurl -sS -X POST \"$IRIS_IDENTITY_URL/v1/api-keys/<key-id>/rotate?graceSeconds=3600\" \\\n  -H \"Authorization: Bearer $IRIS_IDENTITY_TOKEN\"",
      "section_id": "update-rotate-or-revoke-a-key"
    },
    {
      "id": "update-rotate-or-revoke-a-key-ex2",
      "language": "bash",
      "code": "# Revoke: delete the key.\ncurl -sS -X DELETE \"$IRIS_IDENTITY_URL/v1/api-keys/<key-id>\" \\\n  -H \"Authorization: Bearer $IRIS_IDENTITY_TOKEN\"",
      "section_id": "update-rotate-or-revoke-a-key"
    },
    {
      "id": "update-rotate-or-revoke-a-key-ex3",
      "language": "bash",
      "code": "iris identity keys rotate <key-id> --grace 1h --write .env --env <variable-name>\niris identity keys revoke <key-id>",
      "section_id": "update-rotate-or-revoke-a-key"
    },
    {
      "id": "rotate-a-runtime-credential-without-failed-calls-ex0",
      "language": "bash",
      "code": "kubectl -n <namespace-name> create secret generic memory-dp-credential-v2 \\\n     --from-literal=token=\"$(openssl rand -hex 32)\"",
      "section_id": "rotate-a-runtime-credential-without-failed-calls"
    },
    {
      "id": "rotate-a-runtime-credential-without-failed-calls-ex1",
      "language": "yaml",
      "code": "runtime:\n     serviceCredentials:\n       - name: memory-dp\n         subject: memory-dp\n         allowedOperations: [api-key-introspect]\n         allowedProducts: [memory]\n         existingSecret: memory-dp-credential\n       - name: memory-dp-v2\n         subject: memory-dp\n         allowedOperations: [api-key-introspect]\n         allowedProducts: [memory]\n         existingSecret: memory-dp-credential-v2",
      "section_id": "rotate-a-runtime-credential-without-failed-calls"
    },
    {
      "id": "find-who-did-what-ex0",
      "language": "bash",
      "code": "kubectl -n <namespace-name> logs -l app.kubernetes.io/instance=ids --tail=-1 \\\n  | grep '\"audit event\"' | grep '<key-id>'",
      "section_id": "find-who-did-what"
    },
    {
      "id": "upgrade-ex0",
      "language": "bash",
      "code": "helm upgrade ids redis-ai/iris-identity-service \\\n     --version <chart-version> \\\n     --namespace <namespace-name> \\\n     -f values-ids.yaml",
      "section_id": "upgrade"
    },
    {
      "id": "rotate-the-metadata-redis-credential-ex0",
      "language": "bash",
      "code": "kubectl -n <namespace-name> rollout restart deployment/ids-iris-identity-service\n   kubectl -n <namespace-name> rollout status deployment/ids-iris-identity-service",
      "section_id": "rotate-the-metadata-redis-credential"
    },
    {
      "id": "uninstall-ex0",
      "language": "bash",
      "code": "helm uninstall ids --namespace <namespace-name>",
      "section_id": "uninstall"
    }
  ]
}
