Platform

Platform overviewArchitectureWorkflow orchestrationGitOps configurationGovernance and AAAAI and MCPKnowledge and contextRuntime and executionEvidence and monitoring

Use cases

All use casesProduction incidentRelease preparationHotfix to productionSecurity scan triage
Why NopsAIIntegrationsSecurity

Resources

All resourcesAI agent governanceMCP governanceMCP securitySelf-hosted platforms
PricingGitHub

Company

How a run worksAboutContactBook a demo

Access control API

Granting access, revoking it, and asking the platform what a caller may actually do.

ReferenceSecurityAdministratorDeveloper

Key points

  • Effective permissions are the authoritative answer; grants and roles explain how it was reached.
  • inherit decides whether a grant covers one resource or everything nested under it — the setting most often wrong when access is broader than intended.
  • Granting requires owner-level access on the resource, not the role being granted.
  • A resource-use check answers 200 with allowed: false for a denial; a 403 means the caller may not even ask.
  • An authorization team comes from the identity provider; a NopsAI team path is ownership. Granting to one does not change the other.
  • When authorization is unavailable these routes fail closed with 503 rather than guessing.

Operations

GET/v1/access/grantsAuthorized

Lists access grants on a resource or for a subject.

Notes

Grants explain an answer; they are not the answer. Use effective permissions to find out what a caller may actually do.

Parameters

NameInTypeRequiredDescription
resource_typequerystringOptionalRestrict to one resource kind.
resource_idquerystringOptionalRestrict to one resource. Requires resource_type.
subject_typequerystringOptionalRestrict to one kind of subject.Allowed: user, service_account, auth_team
subject_idquerystringOptionalRestrict to one subject.

Call it

List the grants on one pipelineapi-access-control request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" \
  "$NOPSAI_URL/v1/access/grants?resource_type=pipeline&resource_id=platform/release-service" | jq
Result

Every grant that mentions the resource, with the subject and role it binds.

Responses

200application/json

Grants matching the filters.

[{
  "id": "a41c9d02-6f38-4b1d-9c77-51e3a0b4f912",
  "subject_type": "auth_team",
  "subject_id": "platform-sre",
  "subject_display": "Platform SRE",
  "role": "pipeline-operator",
  "resource_type": "pipeline",
  "resource_id": "platform/release-service",
  "inherit": true
}]

When it fails

StatusCauseWhat to do
400A filter combination that cannot be resolved, such as resource_id without resource_type.Supply both halves of a resource filter.
401No usable caller identity.Send a token.
403The caller may not read grants on this resource.Reading who has access is itself an access decision.
404The named resource does not exist.Confirm the resource id.
503Authorization is unavailable.Check AAA.
500The grant query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/access_grants_test.go
  • services/nopsai/access_grants.go
  • services/nopsai/access_grants_handlers.go
POST/v1/access/grantsAuthorized

Grants a subject a role on a resource.

Notes

inherit is the difference between "this pipeline" and "this pipeline and everything under it". It is the setting most often wrong when access is broader than intended.

Call it

Grant a team an operator role on a pipelineapi-access-control request
curl -sX POST "$NOPSAI_URL/v1/access/grants" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"subject_type":"auth_team","subject_id":"platform-sre","role":"pipeline-operator","resource_type":"pipeline","resource_id":"platform/release-service","inherit":true}' | jq
Result

The created grant. inherit: true extends it to resources nested under this one.

Replace before running
  • subject_type is user, service_account, or auth_team.

Responses

201application/json

Grant created.

{
  "id": "a41c9d02-6f38-4b1d-9c77-51e3a0b4f912",
  "subject_type": "auth_team",
  "subject_id": "platform-sre",
  "subject_display": "Platform SRE",
  "role": "pipeline-operator",
  "resource_type": "pipeline",
  "resource_id": "platform/release-service",
  "inherit": true
}

When it fails

StatusCauseWhat to do
400An unknown role, an unusable subject, or a malformed resource reference.The message names the field.
401No usable caller identity.Send a token.
403The caller may not grant access on this resource.Granting requires owner-level access, not the role being granted.
404The subject or the resource does not exist.Confirm both before retrying.
409The same grant already exists.Read the grant list; re-granting is a no-op rather than an update.
503Authorization is unavailable.Check AAA. Grants fail closed.

Side effects

  • Changes what the subject can do immediately.
  • Writes an audit record naming the granter and the grant.

Proven by

  • services/nopsai/access_grants_test.go
  • services/nopsai/aaa_integration_test.go
  • services/nopsai/access_grants.go
DELETE/v1/access/grants/{grantID}Authorized

Revokes an access grant.

Parameters

NameInTypeRequiredDescription
grantIDpathuuidRequiredGrant identifier from the list route.

Call it

Revoke a grantapi-access-control request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/access/grants/$GRANT_ID" -w "%{http_code}\n"
Result

204. The subject loses that access immediately, including for requests already in flight.

Responses

204

Grant revoked.

When it fails

StatusCauseWhat to do
400The id is malformed.Use the id from the grant list.
403The caller may not revoke grants on this resource.Revoking requires owner-level access.
404No grant with that id.It may already be revoked.
503Authorization is unavailable.Check AAA.

Side effects

  • Removes the access immediately.
  • Writes an audit record.

Proven by

  • services/nopsai/access_grants_test.go
  • services/nopsai/access_grants.go
GET/v1/access/effective-permissionsAuthenticated

Resolves what the caller may actually do, after roles, grants, and inheritance.

Notes

This is the route to trust when a caller insists they should have access. Roles and grants explain the answer; this is the answer.

Parameters

NameInTypeRequiredDescription
actionquerystringOptionalNarrow to one AAA action.
resource_typequerystringOptionalNarrow to one resource kind.
resource_idquerystringOptionalNarrow to one resource.
team_pathquerystringOptionalEvaluate within one team path. run_team_path is accepted as an alias.

Call it

Ask whether this caller can run a pipelineapi-access-control request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" \
  "$NOPSAI_URL/v1/access/effective-permissions?action=pipeline.run&resource_type=pipeline&resource_id=platform/release-service" | jq
Result

The resolved answer for this caller — the authoritative one, not a reconstruction from roles.

Responses

200application/json

Effective permissions for the caller, narrowed by whatever filters were supplied.

{
  "caller_type": "user",
  "caller_id": "admin",
  "allowed": true
}

When it fails

StatusCauseWhat to do
400A filter cannot be parsed, or a resource filter is incomplete.Supply resource_type alongside resource_id.
401No usable caller identity.Send a token.
503Authorization is unavailable.Check AAA rather than assuming denial.

Side effects

  • None.

Proven by

  • services/nopsai/aaa_integration_test.go
  • services/nopsai/access_grants_resolve.go
GET/v1/access/teamsAuthenticated

Lists the teams the caller can act within.

Call it

List teams the caller can useapi-access-control request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/access/teams" | jq
Result

Team paths this caller may own resources in — the list a UI renders team pickers from.

Responses

200application/json

Teams available to the caller.

[
  { "id": 12, "path": "platform/payments", "valid": true }
]

When it fails

StatusCauseWhat to do
401No usable caller identity.Send a token.
503Authorization is unavailable.Check AAA.
500The team query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/aaa_integration_test.go
  • services/nopsai/resource_authz.go
GET/v1/access/auth-teamsAuthenticated

Lists the identity-provider teams that can appear as grant subjects.

Notes

An authorization team is a group from the identity provider. A NopsAI team path is ownership. Granting to the first does not change the second.

Call it

List authorization teamsapi-access-control request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/access/auth-teams" | jq
Result

The teams a grant can name with subject_type: auth_team.

Responses

200application/json

Authorization teams known to the platform.

[
  { "id": "platform-sre", "name": "Platform SRE" }
]

When it fails

StatusCauseWhat to do
401No usable caller identity.Send a token.
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/aaa_integration_test.go
  • services/nopsai/access_grants_handlers.go
POST/v1/authz/resource-use/checkAuthenticated

Checks whether a caller may use one resource in a given context.

Notes

Read allowed rather than the status code: a permitted question about a forbidden resource still answers 200.

Call it

Check one resource useapi-access-control request
curl -sX POST "$NOPSAI_URL/v1/authz/resource-use/check" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"action":"model.use","resource_type":"llm_profile","resource_id":"reasoning-large"}' | jq
Result

allowed with the reason, which is what a pipeline editor uses to warn before a run fails.

Responses

200application/json

The decision. A denial is a 200 with allowed: false, not a 403.

{
  "allowed": false,
  "action": "model.use",
  "resource_type": "llm_profile",
  "resource_id": "reasoning-large"
}

When it fails

StatusCauseWhat to do
400The body is malformed or names no action.Send action, resource_type, and resource_id.
403The caller may not ask about this resource.Asking about a resource is itself an access decision.

Side effects

  • None. A check never grants or records access.

Proven by

  • services/nopsai/resource_authz_test.go
  • services/nopsai/resource_authz.go
POST/v1/authz/resource-use/batch-checkAuthenticated

Checks many resource uses in one request.

Notes

This is what the pipeline editor calls before a save, so an author sees "you cannot use this model" while editing rather than when the run fails.

Call it

Check everything a pipeline would touchapi-access-control request
curl -sX POST "$NOPSAI_URL/v1/authz/resource-use/batch-check" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  --data @checks.json | jq
Result

One decision per requested check, in the order they were sent.

Replace before running
  • checks.json is a list of the same objects the single-check route takes.

Responses

200application/json

A decision per check.

[
  { "allowed": true, "action": "model.use", "resource_type": "llm_profile", "resource_id": "reasoning-large" },
  { "allowed": false, "action": "mcp.use", "resource_type": "mcp_profile", "resource_id": "jira-readonly" }
]

When it fails

StatusCauseWhat to do
400The body is malformed or empty.Send a non-empty list of checks.
403The caller may not ask about one of the resources.The whole batch fails rather than silently dropping an entry.

Side effects

  • None.

Proven by

  • services/nopsai/resource_authz_test.go
  • services/nopsai/resource_authz.go
ANY/v1/resources/...Authorized

Reads settings, changes visibility, and adds or removes use grants for a concrete resource.

Notes

Registered as a bare path prefix rather than a method-qualified route, which is why the generated CLI catalogue cannot see it. Handler-authorized: the middleware defers, then the handler resolves the concrete resource and checks owner-level manage access.

Call it

Read the access settings of one resourceapi-access-control request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/resources/llm_profile/reasoning-large" | jq
Result

The resource visibility and its use grants.

Replace before running
  • The path continues with the resource type and identifier.

Responses

200application/json

Resource access settings.

{
  "resource_type": "llm_profile",
  "resource_id": "reasoning-large",
  "visibility": "team"
}

When it fails

StatusCauseWhat to do
404The path does not resolve to a known resource.Check the resource type segment.
405A method the resource surface does not support.Read the resource first to see what it offers.

Side effects

  • Mutating variants change visibility or use grants and write an audit record.

Proven by

  • services/nopsai/resource_authz_test.go
  • services/nopsai/resource_authz.go
  • services/nopsai/routes.go

How it works

Debug access in one order: read effective permissions for the caller, and only then read the grants that produced the answer. Reconstructing the answer from roles and grants by hand is how people convince themselves the platform is wrong.

The batch check exists so an editor can warn while a pipeline is being written rather than when the run fails. It asks about every resource the pipeline would touch — the model, the MCP profiles, the scope — in one request.

Reading who has access is itself an access decision, which is why the list route can answer 403. That is deliberate: an access list is a map of who to compromise.

Implementation evidence

  • services/nopsai/access_grants.go

    Grant documents and handlers.

  • services/nopsai/resource_authz.go

    Resource-use checks and the resource access surface.

  • doc/access-control.md

    AAA service, product roles, and route authorization.