The GitOps endpoints turn a tenant’s decisioning configuration (offers, creatives, channels, qualification rules, contact policies, decision flows, arbitration profiles) into a reviewable YAML/JSON bundle and reconcile a posted bundle back into the database. Reconciliation matches resources byDocumentation Index
Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt
Use this file to discover all available pages before exploring further.
metadata.name rather than by id, so the same bundle promotes across dev / stage / prod where ids differ (src/lib/gitops/types.ts:22-23).
What it does
Three routes share the sameResourceBundle shape declared at src/lib/gitops/types.ts:57-68:
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.
/api/v1/cron/gitops-drift-check runs the diff against a stored snapshot and surfaces drift through cron.schedules.gitopsDriftCheck in helm/values.yaml. See Cron tier.
V1 reconciler scope (per src/lib/gitops/apply.ts:15-19): Category, Channel, ArbitrationProfile, Offer, DecisionFlow. Other resource kinds (SubCategory, Creative, QualificationRule, ContactPolicy) are recognized and validated but report as unchanged — the apply hooks for those kinds are pending.
Quick start
Round-trip a tenant through git:How it works
Bundle format
AResourceBundle is a manifest plus a flat resources[] array. Each resource follows a Kubernetes-like shape (src/lib/gitops/types.ts:9-25):
apiVersion constant lives at src/lib/gitops/types.ts:27. Reconciliation matches by metadata.name, never by id, so the same bundle is promotable across environments.
Authentication
Every route callsrequireTenant(request) (apply/route.ts:54, diff/route.ts:39, export/route.ts:21). 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 to call requireRole.
Audit trail
Real applies (dryRun=false) write a single gitops_apply row to audit_logs summarizing { created, updated, unchanged, kinds } (apply/route.ts:76-95). 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 (diff/route.ts:55-56 calls diffBundle which delegates to applyBundle({ apply: false }) at apply.ts:489-495). 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 request body withawait request.text() and dispatches based on Content-Type (apply/route.ts:26-48):
Content-Type: application/yaml(or any value containingyaml) — body is parsed as YAML byparseBundleYamlfromsrc/lib/gitops/serialize.ts.Content-Type: application/jsonwith a top-levelyamlTextfield — the field is unwrapped and YAML-parsed.Content-Type: application/jsonwith a bareResourceBundleJSON object — parsed byparseBundleJson.- Any other content type — parsed first as JSON, then as YAML on JSON failure.
400 Invalid bundle: Empty request body.
Query parameters
When
"false", mutations are committed and an audit row is written. Any other value (including absent) is treated as a dry run (apply/route.ts:58-59).Reserved — when
true, the reconciler deletes resources that exist in DB but not in the bundle. Currently surfaced via ApplyOptions.prune in lib/gitops/apply.ts:38 but not enforced for every kind in V1.Comma-separated
ResourceKind filter — only the listed kinds are reconciled. Valid values per src/lib/gitops/types.ts:29-38: Category, SubCategory, Offer, Creative, Channel, QualificationRule, ContactPolicy, DecisionFlow, ArbitrationProfile.Response
Returned atapply/route.ts:97. Shape declared at src/lib/gitops/types.ts:83-95.
Mirrors the
dryRun query parameter — true for a preview, false when changes were committed.Per-resource diff. Each entry is
{ kind, name, op, changedFields?, before?, after? } per src/lib/gitops/types.ts:72-81. op is one of "create" | "update" | "delete" | "unchanged".Counts across all diffs:
{ created, updated, deleted, unchanged }.Status codes
| Code | When |
|---|---|
| 200 | Apply or dry-run succeeded |
| 400 | Invalid bundle (parse error, empty body, schema violation) |
| 401 | Missing tenant context |
| 403 | Invalid tenant identifier |
| 500 | Reconciler raised — full message in error.message |
POST /api/v1/gitops/diff
Computes the diff against the live DB without mutating. Same body formats as apply.
Query parameters
Same comma-separated filter as
apply. Other apply-only flags (dryRun, prune) are not read by this route.Response
SameApplyResult shape as apply with dryRun: true always (diff/route.ts:55-56).
Status codes
| Code | When |
|---|---|
| 200 | Diff computed |
| 400 | Invalid bundle |
| 401 / 403 | Missing or invalid tenant context |
| 500 | Reconciler raised |
GET /api/v1/gitops/export
Dumps the tenant’s current decisioning state as a ResourceBundle.
Query parameters
yaml (default) or json. Read at export/route.ts:25.bundle (default) returns one document. files returns a map of relative path → file content via bundleToFiles from lib/gitops/serialize.ts, suitable for writing to disk for git review (export/route.ts:31-46).Response headers
bundle layout sets Content-Disposition: attachment; filename="kaireon-export-<tenantId>.<ext>" (export/route.ts:42, 53, 60). YAML responses use Content-Type: application/yaml.
Response body
Bundle layout (default) — a single YAML or JSON document perResourceBundle shape:
files map plus the original manifest:
Status codes
| Code | When |
|---|---|
| 200 | Export succeeded |
| 401 / 403 | Missing or invalid tenant context |
| 500 | Export raised — full message in error.message |
Required headers
| Header | Required | Read at | Purpose |
|---|---|---|---|
Content-Type | Yes (apply / diff) | Next.js framework | application/yaml, application/json, or any value containing yaml |
X-API-Key | Yes (one of the two) | tenant.ts:97 | API key (krn_…) |
X-Tenant-Id | Yes (one of the two) | tenant.ts:113 | Direct tenant id |
Configuration
Drift detection
/api/v1/cron/gitops-drift-check runs the diff against a stored bundle snapshot on a schedule defined at helm/values.yaml:
File layout for git
Recommended directory structure when usinglayout=files:
bundleToFiles — operators do not need to invent the layout.
Honest limits
- Reconciler scope in V1 covers
Category,Channel,ArbitrationProfile,Offer,DecisionFlow(apply.ts:15-17). The other recognized kinds (SubCategory,Creative,QualificationRule,ContactPolicy) parse and validate but always report asunchanged— adding them is a per-kind case inapplyResource. - 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 callrequireRole(request, "admin", "editor"). prune=trueis accepted as a query parameter and threaded intoApplyOptions.prune, 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 (
apply/route.ts:91-95). The reconciliation itself still commits — if the audit row matters for compliance, monitoraudit_logsingestion 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 fullmanifest + resourcesshape.
Related
- Cron tier — runs the daily
/api/v1/cron/gitops-drift-check. - Audit Logs —
gitops_applyaction rows record every real apply. - Decision Flows — one of the reconciler-supported resource kinds.
- Offers — one of the reconciler-supported resource kinds.