Skip to main content
The GitOps endpoints turn a tenant’s decisioning configuration (offers, creatives, channels, decisioning gates, contact policies, decision flows, ranking profiles) into a reviewable YAML/JSON bundle and reconcile a posted bundle back into the database. Reconciliation matches resources by metadata.name rather than by id, so the same bundle promotes across dev / stage / prod where ids differ.

What it does

Three routes share the same bundle shape:
  • GET /api/v1/gitops/export — dump the tenant’s current state as YAML or JSON, optionally split into per-resource files.
  • POST /api/v1/gitops/diff — compute the diff between a posted bundle and the live DB without mutating.
  • POST /api/v1/gitops/apply — reconcile a posted bundle into the DB, dry-run by default.
A separate scheduled cron at /api/v1/cron/gitops-drift-check runs the diff against a stored snapshot and surfaces drift through the cron schedule configuration. See Cron tier. V1 reconciler scope — applied through to the database:
Other resource kinds — recognized and validated, but always reported as unchanged:
Apply support for the second group is pending.

Quick start

Round-trip a tenant through git:

How it works

Bundle format

A bundle is a manifest plus a flat resources[] array. Each resource follows a Kubernetes-like shape:
The apiVersion value is always kaireonai.com/v1. Reconciliation matches by metadata.name, never by id, so the same bundle is promotable across environments.
The Channel resource spec.status accepts draft, active, paused, or archived (it defaults to active when omitted). draft is accepted because Channel.status defaults to draft in the database — without it, a tenant with any draft channel would export a bundle its own diff/apply endpoints then reject. Draft channels therefore round-trip cleanly through export → apply.

Authentication

Every route requires a tenant identifier on the request. The tenant binds via X-API-Key (preferred — also used as the rate-limit identifier) or X-Tenant-Id. Missing tenant returns 401; invalid tenant returns 403. The current implementation does not enforce a role gate beyond tenant binding — a viewer-tier API key can apply changes. Production deployments wire role enforcement via an upstream proxy or by extending the route with role-based authorization.

Audit trail

Real applies (dryRun=false) write a single gitops_apply row to the audit trail summarizing { created, updated, unchanged, kinds }. The audit write is best-effort — if it fails the apply still returns success.

Diff-then-apply pattern

POST /api/v1/gitops/diff is the same code path as POST /api/v1/gitops/apply?dryRun=true — the dedicated diff endpoint runs the reconciler in preview mode and returns the same response shape. The dedicated diff endpoint is convenience — no body parsing differences, identical validation.

Reference

POST /api/v1/gitops/apply

Reconciles a posted bundle into the tenant’s DB. Dry-run by default.

Body formats accepted

The route reads the raw request body and dispatches based on Content-Type:
  • Content-Type: application/yaml (or any value containing yaml) — body is parsed as YAML.
  • Content-Type: application/json with a top-level yamlText field — the field is unwrapped and YAML-parsed.
  • Content-Type: application/json with a bare bundle JSON object — parsed as JSON.
  • Any other content type — parsed first as JSON, then as YAML on JSON failure.
An empty body returns 400 Invalid bundle: Empty request body.

Query parameters

string
default:"true"
When "false", mutations are committed and an audit row is written. Any other value (including absent) is treated as a dry run.
string
default:"false"
Reserved — when true, the reconciler deletes resources that exist in DB but not in the bundle. Accepted as a query parameter today but not enforced for every kind in V1.
string
Comma-separated kind filter — only the listed kinds are reconciled. Valid values:

Response

Body shape:
boolean
Mirrors the dryRun query parameter — true for a preview, false when changes were committed.
array
Per-resource diff. Each entry is { kind, name, op, changedFields?, before?, after? }. op is one of "create" | "update" | "delete" | "unchanged".
object
Counts across all diffs: { created, updated, deleted, unchanged }.

Status codes


POST /api/v1/gitops/diff

Computes the diff against the live DB without mutating. Same body formats as apply.

Query parameters

string
Same comma-separated filter as apply. Other apply-only flags (dryRun, prune) are not read by this route.

Response

Same response shape as apply with dryRun: true always.

Status codes


GET /api/v1/gitops/export

Dumps the tenant’s current decisioning state as a bundle.

Query parameters

string
default:"yaml"
yaml (default) or json.
string
default:"bundle"
bundle (default) returns one document. files returns a map of relative path → file content, suitable for writing to disk for git review.

Response headers

bundle layout sets Content-Disposition: attachment; filename="kaireon-export-<tenantId>.<ext>". YAML responses use Content-Type: application/yaml.

Response body

Bundle layout (default) — a single YAML or JSON document with the bundle shape:
Files layout — JSON response with a files map plus the original manifest:

Status codes

Required headers

Configuration

Drift detection

/api/v1/cron/gitops-drift-check runs the diff against a stored bundle snapshot on a schedule defined in your Helm values:
The cron does not run apply — it only surfaces drift through metrics and audit. See Cron tier.

File layout for git

Recommended directory structure when using layout=files:
The exact filenames are returned by the export endpoint — operators do not need to invent the layout.

Honest limits

  • Reconciler scope in V1 covers five kinds: Category, Channel, Offer, DecisionFlow, and RankingProfile. The other recognized kinds (SubCategory, Creative, QualificationRule, ContactPolicy) parse and validate but always report as unchanged — apply support for those kinds is on the roadmap.
  • The current routes enforce tenant binding but not a role gate. A viewer-tier API key can call apply with dryRun=false. Production deployments either wire role enforcement upstream or extend the route to require an admin or editor role.
  • prune=true is accepted as a query parameter, but full prune-by-kind enforcement is not yet implemented for every kind. Treat prune as opt-in and verify behavior per kind before enabling.
  • The audit-log write inside the apply path is best-effort and silenced on failure. The reconciliation itself still commits — if the audit row matters for compliance, monitor audit-log ingestion separately.
  • An empty request body is rejected with 400 Invalid bundle: Empty request body, but a body containing only {} will pass through to the YAML parser fallback and surface a less specific error. Senders should always include the full manifest + resources shape.
  • Cron tier — runs the daily /api/v1/cron/gitops-drift-check.
  • Audit Logsgitops_apply action rows record every real apply.
  • Decision Flows — one of the reconciler-supported resource kinds.
  • Offers — one of the reconciler-supported resource kinds.