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

GitOps and config sync API

Connecting configuration repositories, syncing from them, reviewing drift, and pushing desired state back.

ReferenceAdministratorDeveloperOperator

Key points

  • Two scopes, one shape: the global repository holds system settings, a team repository holds that team’s pipelines, steps, schedules, scopes, and triggers. Both expose read, sync, drift, write, and validate.
  • A sync is asynchronous and singular. Starting one while another runs answers 409 — that is the platform refusing to run two, not a bad request.
  • Cancelling mid-sync leaves a partial import rather than rolling back.
  • Disconnecting a repository does not remove configuration; it removes the source of truth, and the documents become database-owned.
  • Drift carries both git_content and desired_content per file, which is what the side-by-side review renders.
  • The write routes are the only ones here that change something outside the platform.

Operations

GET/v1/system/config-repoAuthorized

Reads the global configuration repository connection.

Notes

A disconnected install answers 200 with an empty document rather than 404, so a client can read unconditionally.

Call it

Read the global repositoryapi-gitops request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repo" | jq
Result

The repository, branch, base path, and credential reference. The credential value is never returned.

Responses

200application/json

The configured global repository, or an empty document when none is connected.

{
  "repository": "https://git.example.com/platform/nopsai-config",
  "branch": "main",
  "base_path": "",
  "credential_ref": "platform/git-config"
}

Side effects

  • None.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_repositories_schema.go
PUT/v1/system/config-repoAuthorized

Connects or replaces the global configuration repository.

Call it

Connect the global repositoryapi-gitops request
curl -sX PUT "$NOPSAI_URL/v1/system/config-repo" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"repository":"https://git.example.com/platform/nopsai-config","branch":"main","credential_ref":"platform/git-config"}' | jq
Result

The stored connection. Nothing is imported until a sync runs.

Replace before running
  • credential_ref names a stored credential with read access to the repository.

Responses

200application/json

Connection stored.

{
  "repository": "https://git.example.com/platform/nopsai-config",
  "branch": "main",
  "credential_ref": "platform/git-config"
}

When it fails

StatusCauseWhat to do
400An unreachable repository, an unknown branch, or a credential that cannot read it.The connection is checked before it is stored.
500The connection could not be saved.Retry.

Side effects

  • Stores the connection. Configuration is imported only when a sync runs.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_repositories_schema.go
DELETE/v1/system/config-repoAuthorized

Disconnects the global configuration repository.

Notes

Disconnecting does not remove configuration. It removes the source of truth, which is a different and easily surprising thing.

Call it

Disconnect the global repositoryapi-gitops request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repo" -w "%{http_code}\n"
Result

Already-imported configuration stays; it simply stops being refreshed from Git.

Responses

204

Disconnected.

When it fails

StatusCauseWhat to do
500The disconnect could not be persisted.Retry.

Side effects

  • Imported documents remain in the database and become database-owned.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_repositories_schema.go
POST/v1/system/config/syncAuthorized

Starts a configuration sync across every connected repository.

Notes

A sync is asynchronous and singular: the 409 is the platform refusing to run two at once, not an error in your request.

Call it

Sync everythingapi-gitops request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config/sync" | jq
Result

202. The sync runs asynchronously; poll the status route for the result.

Responses

202application/json

Sync started.

{"state":"running"}

When it fails

StatusCauseWhat to do
409A sync is already running.Poll the status route rather than starting a second one.

Side effects

  • Imports pipelines, steps, schedules, scopes, triggers, knowledge, dashboards, and system settings from Git.
  • Documents removed from Git are removed from the platform.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_sync.go
GET/v1/system/config/syncAuthorized

Reads the current sync status.

Notes

Safe to poll: it reads recorded state and does not touch Git.

Call it

Poll sync statusapi-gitops request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config/sync" | jq
Result

The state, the last successful sync, and the commit it imported.

Responses

200application/json

Sync status.

{
  "state": "idle",
  "last_sync_at": "2026-08-19T09:58:12Z",
  "last_commit_sha": "4f2a91c",
  "message": "synced 42 documents"
}

Side effects

  • None.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_sync.go
POST/v1/system/config/sync/cancelAuthorized

Cancels a running sync.

Notes

Cancelling mid-sync leaves a partial import rather than rolling back. Run another sync to reach a consistent state.

Call it

Cancel a syncapi-gitops request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config/sync/cancel" | jq
Result

The sync stops. Documents already imported stay imported.

Responses

200application/json

Cancellation accepted.

{"state":"cancelled"}

Side effects

  • Stops the run; already-imported documents remain.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_sync.go
GET/v1/system/config-repo/syncAuthorized

Reads sync status for the global repository specifically.

Notes

Use this when several repositories are connected and you need to know which one failed.

Call it

Poll the global repository syncapi-gitops request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repo/sync" | jq
Result

The same status shape, narrowed to the global repository.

Responses

200application/json

Global repository sync status.

{
  "state": "idle",
  "last_sync_at": "2026-08-19T09:58:12Z",
  "last_commit_sha": "4f2a91c",
  "message": "synced 42 documents"
}

Side effects

  • None.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_sync.go
POST/v1/system/config-repo/syncAuthorized

Syncs the global repository only.

Call it

Sync the global repositoryapi-gitops request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repo/sync" | jq
Result

202, with team repositories untouched.

Responses

202application/json

Sync started for the global repository.

{"state":"running"}

When it fails

StatusCauseWhat to do
409A sync is already running.Poll the status route.

Side effects

  • Imports system settings and global documents.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_sync.go
POST/v1/system/config-repo/sync/cancelAuthorized

Cancels the global repository sync.

Notes

As with the platform-wide cancel, a partial import is left in place.

Call it

Cancel the global syncapi-gitops request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repo/sync/cancel" | jq
Result

The global sync stops.

Responses

200application/json

Cancellation accepted.

{"state":"cancelled"}

Side effects

  • Stops the run; already-imported documents remain.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_sync.go
GET/v1/system/config-repo/driftAuthorized

Compares Git against desired state for the global repository.

Notes

Each item carries git_content and desired_content, which is what the UI renders as a side-by-side diff before a push.

Call it

Read global driftapi-gitops request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repo/drift" | jq '{summary, can_push}'
Result

Per-file status with both contents, plus whether the difference can be pushed back.

Responses

200application/json

Drift between the repository and the platform.

{
  "base_branch": "main",
  "push_branch": "nopsai/config-sync",
  "items": [
    {
      "path": "pipelines/platform/release-service.yaml",
      "status": "modified",
      "git_content": "name: release-service\n...",
      "desired_content": "name: release-service\n..."
    }
  ],
  "summary": { "modified": 1 },
  "can_push": true,
  "push_message": "Sync 1 change from NopsAI"
}

Side effects

  • None. Reading drift never writes to Git.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_repository_drift.go
  • services/nopsai/internal/configsync/diff.go
POST/v1/system/config-repo/writeAuthorized

Pushes desired state back to the global repository.

Notes

This is the one route in the area that changes something outside the platform. Read drift first.

Call it

Push drift back to Gitapi-gitops request
curl -sX POST "$NOPSAI_URL/v1/system/config-repo/write" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"push_message":"Sync settings from NopsAI"}' | jq
Result

A commit on the push branch. Read drift first: this writes to a real repository.

Responses

200application/json

Changes written to the push branch.

{
  "branch": "nopsai/config-sync",
  "commit_sha": "9b31f0c",
  "written": 1
}

When it fails

StatusCauseWhat to do
400Nothing to write, or the drift cannot be pushed.Check can_push and push_message on the drift response.
500The push failed at the provider.Check the credential’s write access to the push branch.

Side effects

  • Commits to the configured push branch of a real repository.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_repository_drift.go
POST/v1/system/config-repo/validateAuthorized

Validates configuration documents without storing or pushing them.

Notes

Validating a whole file set at once is what a CI job should call on a configuration repository pull request.

Call it

Validate a set of configuration filesapi-gitops request
curl -sX POST "$NOPSAI_URL/v1/system/config-repo/validate" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"base_path":"","files":[{"path":"setting/system/auth.yaml","content":"local_enabled: true\n"}]}' | jq
Result

The valid, errors, warnings shape, with each issue carrying the file it came from.

Responses

200application/json

Validation ran. Read valid rather than the status code.

{
  "valid": true,
  "errors": [],
  "warnings": []
}

When it fails

StatusCauseWhat to do
400The payload is not valid JSON or names an unusable base path.Send base_path and a files list.

Side effects

  • None.

Proven by

  • services/nopsai/config_repositories_schema_test.go
  • services/nopsai/validation_handlers.go
GET/v1/system/config-reposAuthorized

Lists every connected configuration repository.

Call it

List configuration repositoriesapi-gitops request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repos" | jq
Result

The global repository and every team repository, with their branches and last sync.

Responses

200application/json

All connected repositories.

[
  {
    "id": 1,
    "scope": "global",
    "repository": "https://git.example.com/platform/nopsai-config",
    "branch": "main"
  },
  {
    "id": 2,
    "scope": "team",
    "team_path": "platform",
    "repository": "https://git.example.com/platform/team-config",
    "branch": "main"
  }
]

When it fails

StatusCauseWhat to do
500The query failed.Platform fault.

Side effects

  • None.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_repositories_schema.go
POST/v1/system/config-repos/syncAuthorized

Syncs every connected repository.

Call it

Sync all repositoriesapi-gitops request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repos/sync" | jq
Result

202. Equivalent to the platform-wide sync, addressed by repository rather than by configuration.

Responses

202application/json

Sync started.

{"state":"running"}

When it fails

StatusCauseWhat to do
409A sync is already running.Poll the status route.

Side effects

  • Imports from every connected repository.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_sync.go
POST/v1/system/config-repos/sync/cancelAuthorized

Cancels the all-repository sync.

Notes

Partial imports are left in place; run another sync to converge.

Call it

Cancel the all-repository syncapi-gitops request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/system/config-repos/sync/cancel" | jq
Result

The sync stops, leaving whatever was already imported.

Responses

200application/json

Cancellation accepted.

{"state":"cancelled"}

Side effects

  • Stops the run.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_sync.go
GET/v1/teams/{teamID}/config-repositoryAuthorized

Reads a team’s configuration repository connection.

Notes

A team repository owns that team’s pipelines, steps, schedules, scopes, and triggers. The global repository owns system settings.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Read a team repositoryapi-gitops request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository" | jq
Result

The team’s repository, branch, and base path, or an empty document when none is connected.

Responses

200application/json

The team connection.

{
  "repository": "https://git.example.com/platform/team-config",
  "branch": "main",
  "base_path": "",
  "credential_ref": "platform/git-config"
}

When it fails

StatusCauseWhat to do
405A method the surface does not support.GET, PUT, and DELETE are supported here.

Side effects

  • None.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/team_handlers.go
PUT/v1/teams/{teamID}/config-repositoryAuthorized

Connects or replaces a team’s configuration repository.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Connect a team repositoryapi-gitops request
curl -sX PUT "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"repository":"https://git.example.com/platform/team-config","branch":"main","credential_ref":"platform/git-config"}' | jq
Result

The stored connection. Nothing is imported until the team sync runs.

Responses

200application/json

Connection stored.

{
  "repository": "https://git.example.com/platform/team-config",
  "branch": "main"
}

When it fails

StatusCauseWhat to do
400The repository or credential cannot be used.The connection is checked before it is stored.
405An unsupported method.Use GET, PUT, or DELETE.

Side effects

  • Stores the connection.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/team_handlers.go
DELETE/v1/teams/{teamID}/config-repositoryAuthorized

Disconnects a team’s configuration repository.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Disconnect a team repositoryapi-gitops request
curl -sX DELETE -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository" -w "%{http_code}\n"
Result

The team’s imported documents stay and become database-owned.

Responses

204

Disconnected.

When it fails

StatusCauseWhat to do
405An unsupported method.Use GET, PUT, or DELETE.

Side effects

  • Imported documents remain and stop being refreshed from Git.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/team_handlers.go
POST/v1/teams/{teamID}/config-repository/syncAuthorized

Syncs one team’s repository.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Sync a team repositoryapi-gitops request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository/sync" | jq
Result

202. Only this team’s documents are touched.

Responses

202application/json

Sync started for the team.

{"state":"running"}

When it fails

StatusCauseWhat to do
409A sync is already running for this team.Poll the team sync status.

Side effects

  • Imports the team’s pipelines, steps, schedules, scopes, and triggers.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/team_handlers.go
GET/v1/teams/{teamID}/config-repository/syncAuthorized

Reads a team’s sync status.

Notes

Safe to poll.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Poll a team syncapi-gitops request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository/sync" | jq
Result

State, last sync, and the commit imported for this team.

Responses

200application/json

Team sync status.

{
  "state": "idle",
  "last_sync_at": "2026-08-19T09:58:12Z",
  "last_commit_sha": "4f2a91c",
  "message": "synced 42 documents"
}

Side effects

  • None.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/team_handlers.go
POST/v1/teams/{teamID}/config-repository/sync/cancelAuthorized

Cancels a team’s running sync.

Notes

Partial imports are left in place.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Cancel a team syncapi-gitops request
curl -sX POST -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository/sync/cancel" | jq
Result

The team sync stops, leaving whatever was already imported.

Responses

200application/json

Cancellation accepted.

{"state":"cancelled"}

Side effects

  • Stops the run.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/team_handlers.go
GET/v1/teams/{teamID}/config-repository/driftAuthorized

Compares a team’s repository against desired state.

Notes

Drift appears whenever someone edits a GitOps-managed document through the UI or API: the database becomes the owner until it is pushed or discarded.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Read team driftapi-gitops request
curl -s -H "Authorization: Bearer $NOPSAI_TOKEN" "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository/drift" | jq '{summary, can_push}'
Result

The same drift shape as the global repository, scoped to this team.

Responses

200application/json

Drift for the team repository.

{
  "base_branch": "main",
  "push_branch": "nopsai/config-sync",
  "items": [
    {
      "path": "pipelines/platform/release-service.yaml",
      "status": "modified",
      "git_content": "name: release-service\n...",
      "desired_content": "name: release-service\n..."
    }
  ],
  "summary": { "modified": 1 },
  "can_push": true,
  "push_message": "Sync 1 change from NopsAI"
}

Side effects

  • None.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_repository_drift.go
POST/v1/teams/{teamID}/config-repository/writeAuthorized

Pushes a team’s desired state back to Git.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Push team drift back to Gitapi-gitops request
curl -sX POST "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository/write" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"push_message":"Sync platform pipelines from NopsAI"}' | jq
Result

A commit on the team’s push branch.

Responses

200application/json

Changes written.

{
  "branch": "nopsai/config-sync",
  "commit_sha": "1a7de40",
  "written": 3
}

When it fails

StatusCauseWhat to do
400Nothing to write, or drift that cannot be pushed.Check can_push on the drift response.
500The push failed at the provider.Check write access on the push branch.

Side effects

  • Commits to a real repository.
  • Writes an audit record.

Proven by

  • services/nopsai/config_sync_test.go
  • services/nopsai/config_repository_drift.go
POST/v1/teams/{teamID}/config-repository/validateAuthorized

Validates a team’s configuration documents without storing them.

Parameters

NameInTypeRequiredDescription
teamIDpathintegerRequiredTeam identifier.

Call it

Validate team configuration filesapi-gitops request
curl -sX POST "$NOPSAI_URL/v1/teams/$TEAM_ID/config-repository/validate" \
  -H "Authorization: Bearer $NOPSAI_TOKEN" \
  -H "Content-Type: application/json" \
  --data @files.json | jq
Result

The valid, errors, warnings shape, with each issue naming its file.

Replace before running
  • files.json carries base_path and a list of {path, content} documents.

Responses

200application/json

Validation ran.

{
  "valid": true,
  "errors": [],
  "warnings": []
}

When it fails

StatusCauseWhat to do
400The payload could not be read.Send base_path and files.

Side effects

  • None.

Proven by

  • services/nopsai/config_repositories_schema_test.go
  • services/nopsai/validation_handlers.go

How it works

Drift is not a fault. It is what happens when someone edits a GitOps-managed document through the UI or API: the database becomes the owner until the change is pushed back or discarded. The question a reviewer answers is which of the two contents should win.

Validate a whole file set rather than a document at a time. That is the call a CI job should make on a configuration repository pull request, and it reports every issue with the file it came from.

When several repositories are connected, the per-repository status routes are how you find which one failed. The platform-wide status tells you a sync failed, not where.

Implementation evidence

  • services/nopsai/config_sync.go

    Sync orchestration and status.

  • services/nopsai/config_repository_drift.go

    Drift comparison and push behaviour.

  • services/nopsai/internal/configsync/diff.go

    Per-file drift item shape.