EQTY LabDocs

API Keys and Service Accounts

Machine authentication with API keys, and how service accounts for backend workers are set up.

View as Markdown

API keys and service accounts support machine-to-machine access. They should be used only when an interactive user session is not appropriate.

API Keys

API keys are tied to platform access and should be treated as secrets. An API key acts on behalf of the user who created it, limited to the scopes chosen at creation, so it is sometimes called a user API key. Use them for integrations that act with a defined platform identity.

  • Empty scopes keep legacy unrestricted behavior, in which the key impersonates the user. They do not enroll gateway agents.
  • Named scopes are RBAC permission names the creator can grant (for example register_agents, view_project_data), plus did:read when the caller is not a pure agent operator. A scoped key is deny-by-default outside that list. revoke_agents and restore_agents are never accepted as scopes, because a key cannot act as a platform admin, and neither is certify_agents, which belongs to the governance worker service account alone.
  • * is explicit full access, available to organization owners only.
  • projectId binds project-level permissions to one project. Organization-level actions such as register_agents still apply across the organization, except view_agent_certifications, submit_agent_assessments and review_agent_assessments, which a project-bound key cannot carry. Omit projectId (Organization) for every project the holder can already access.
  • API keys cannot call any /api/v1/api-keys route, so a key cannot create, list, read, or revoke keys. Manage keys with a user session.
  • allowedIps limits the key to the listed IP addresses or CIDR ranges, matched against the address Auth Service sees the request come from.
  • Name, description, and expiry are required at create time. expiresAt must be in the future and no more than 365 days out; a key with a passed expiry reports status expired even though it is still stored as active.

A project binding must be paired with named scopes. Sending projectId with empty scopes, or with *, is rejected at create time: neither one names the organization-level actions the binding cannot take away, so the binding would advertise a narrower reach than the key actually has. Naming those scopes explicitly is still accepted — register_agents alongside a projectId is a deliberate choice, not an accident. The exceptions are view_agent_certifications, submit_agent_assessments and review_agent_assessments: each is refused alongside a projectId, because agents’ certifications and assessments are served only to a key bound to no project.

For enrollment, a key with register_agents or * may call the gateway with Authorization: Bearer ak_…. Both the key scope and the owner’s RBAC grant are required, and empty-scope keys cannot enroll. Create the key from Guardian (Settings → API Keys) or the Auth Service API using a user session.

In Guardian, the create dialog requires at least one named scope and no longer offers Full access (*). An organization owner who needs full access can still send "scopes": ["*"] to the Auth Service API. Choosing Project access hides the scopes a binding cannot narrow — organization-level actions and did:read — so switch to Organization to include them.

Recommended practices:

  • Store keys in a secrets manager.
  • Prefer a short expiry and one key per agent or pipeline.
  • Rotate keys when ownership or system responsibility changes.
  • Revoke unused keys. Revoking a key does not unwind agents already enrolled with it, and an API key cannot revoke or restore an agent regardless of its scopes — that action requires a signed-in organization owner.
  • Avoid sharing a single key across unrelated integrations.

Integrity Service and the SDK

The Integrity Service accepts Authorization: Bearer ak_… on every route, so the Python SDK (eqty_sdk) and other integrations can sign with an auth-service signer and register lineage with one key and no user session. Each route is attributed to a project and checked against the key’s scopes and binding through Auth Service RBAC.

  • Registering a graph or statement under a project needs create_declarations on that project, and view_project_data to read it back. Bind the key to the project when the integration only ever writes to one. Registering a new project root needs create_projects, which is organization-level.
  • Storing a blob needs create_declarations or create_comments in any project the key reaches, and reading one needs view_project_data in any project.
  • A key writing outside its binding, or without the permission, gets 403 Forbidden naming the permission it lacks. An unregistered graph ID is also a 403 on writes, so a key cannot probe for graph IDs.
  • GET /graph/v1 lists only the project trees the key can view. A key with no view_project_data anywhere gets an empty list rather than an error.
  • PUT /dynamic-config/v1 (manage_org_settings) stays user-session only.
  • If Auth Service is unreachable while a check runs, the request is refused with 503 Service Unavailable; retry it. A 403 is a policy decision and will not change on retry.

A typical SDK key for a pipeline is create_declarations plus view_project_data, bound to the project, with a short expiry. GET /api/v1/rbac/permission/{permission}/projects on the Auth Service reports the projects where a key or user holds a permission, after scopes and binding are applied, which is useful for checking a key before handing it to a pipeline. Send the key as a bearer token:

bash
curl "https://guardian.example.com/authService/api/v1/rbac/permission/create_declarations/projects" \
  -H "Authorization: Bearer $GUARDIAN_API_KEY"
json
{
  "permission": "create_declarations",
  "projects": [{ "id": 15, "uuid": "fd47f68b-8760-4d0d-94fa-7658924acb25" }]
}

An empty projects list means the key cannot use that permission anywhere.

Governance Service

The Governance Service checks a key’s scopes and binding through Auth Service RBAC, as it checks a person’s permissions. The routes that act on who a person is to a program or an assessment accept a key bound to no project that has empty scopes or *, or the scope for that standing:

  • submit_agent_assessments: start an assessment, and pin, declare, claim controls not applicable in and submit one the key’s holder started.
  • review_agent_assessments: review a control of an assessment of a program the holder reviews, ask again for a certification their review rests on, and revoke one they authorized.
  • manage_programs: assign and remove a program’s reviewers, revoke any certification, and ask again for any certification except one of an assessment the holder started or a submission they made.

A scope counts only while the holder still holds that permission in the organization, and their standing still decides, as it does for their sign-in. Assigning a reviewer looks the member up in Auth Service with the key, which only a key with empty scopes or * may do. Signing a certification with the platform key takes a key whose scopes cover every permission of a role that may sign.

Control Plane

The Control Plane accepts Authorization: Bearer ak_… on the routes that ask Auth Service RBAC for a permission. Unlike the Integrity Service, it does not accept keys everywhere: most routes gated on identity alone still require a JWT. Control Plane Authentication and RBAC covers the permission each route checks.

  • Minting an agent registration token needs register_agents, so a headless Viper enrollment can use a key instead of a Guardian sign-in.

  • The project-scoped agent, trust, and membership routes are checked against the key’s scopes and binding for that project.

  • GET /v1/agents requires project_uuid when it is called with a key, so the same view_project_data check applies and the results are limited to that project. Filtering it with has_profile=true also needs a key that could read those profiles: one bound to no project, with empty scopes, * or submit_agent_assessments. Any other key gets 403 Forbidden.

  • A key never acts as a platform admin, even when its owner is one. Admin-only routes — creating or revoking agents, managing plugin secrets, and listing users — return 403 Forbidden for any key.

  • Per-agent routes under /v1/agents/{id} accept a key bound to no project that has empty scopes or *, which acts as its holder there, or the scope for what the route does, for the agents its holder is bound to:

    • submit_agent_assessments reads the agent: its detail, configurations, state history and the files a state references, definitions, tools and operating profile.
    • view_agent_certifications reads GET /v1/agents/{id}/certifications, so deploy automation can pull an agent’s certifications.
    • manage_programs or review_agent_assessments attach a certification, as the Governance Service does for whoever asked for it.

    A scope counts only while the holder still holds that permission in the key’s organization: Auth Service validation reports those as keyHeldScopes, beside the keyScopes the key was created with, and the Control Plane acts on nothing else. A state’s raw snapshot and findings, audit exchanges, plugin events and saving an agent’s profile take empty scopes or *. A project-bound key, or one without the route’s scope, gets 403 Forbidden.

  • Other routes gated on identity alone (/v1/me, plugin catalog and configuration reads) refuse keys with 403 until each is mapped to a permission. Use a JWT for those.

Service Accounts

Service accounts are for trusted platform services, such as backend workers that call another platform service, and for approved backend integrations. Operators configure service accounts as part of deployment and operations, not as an ad hoc user workflow.

Do not use service accounts for normal human user access. Human users should sign in through the identity provider and receive project roles.

Setting Up Service Accounts

Auth Service creates one service account, governance-worker, at startup from the provider’s client credentials:

  • Auth0: AUTH0_GOVERNANCE_WORKER_CLIENT_ID and AUTH0_GOVERNANCE_WORKER_CLIENT_SECRET, plus AUTH0_GOVERNANCE_WORKER_AUDIENCE (defaults to IDP_AUTH0_API_IDENTIFIER).
  • Keycloak: KEYCLOAK_GOVERNANCE_WORKER_CLIENT_ID and KEYCLOAK_GOVERNANCE_WORKER_CLIENT_SECRET. Without a secret, Auth Service creates the client in Keycloak.
  • Microsoft Entra: ENTRA_GOVERNANCE_WORKER_CLIENT_ID and ENTRA_GOVERNANCE_WORKER_CLIENT_SECRET, plus ENTRA_GOVERNANCE_WORKER_SCOPE.

The chart sets these from global.secrets.governanceWorker while auth-service.config.serviceAccounts.governanceWorker.enabled is true (the default). Missing credentials or a failed creation is only logged, and Auth Service still starts, so check its startup logs. A platform service gets the account’s token from GET /api/v1/service-accounts/governance-worker/token, authenticated with API_SECRET as a bearer token.

After setup, confirm the service account can perform only the workflow it was created for. If a service account fails, check its configured secret, service name, role expectations, and Auth Service health.

Store service-account credentials in the deployment secret system. Do not reuse service-account secrets across unrelated integrations, and rotate credentials after suspected exposure. To rotate the governance worker’s secret, issue a new one in the identity provider, update platform-governance-worker, and restart Auth Service, which stores the configured secret at startup.

Choosing Between Them

Use a user API key when the integration should be accountable to a user or customer-managed integration identity. Use a service account when the integration is a platform service that must operate independently of a human user.