Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt

Use this file to discover all available pages before exploring further.

The GitOps endpoints turn a tenant’s decisioning configuration (offers, creatives, channels, qualification rules, 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:
Category, Channel, Offer, DecisionFlow, RankingProfile
Other resource kinds — recognized and validated, but always reported as unchanged:
SubCategory, Creative, QualificationRule, ContactPolicy
Apply support for the second group is pending.

Quick start

Round-trip a tenant through git:
# 1. Export current state to YAML
curl https://playground.kaireonai.com/api/v1/gitops/export \
  -H "X-API-Key: krn_your_api_key" \
  -H "X-Tenant-Id: 5a9904b9-..." \
  -o tenant-bundle.yaml

# 2. Edit a file, commit to git, then preview the diff
curl -X POST https://playground.kaireonai.com/api/v1/gitops/diff \
  -H "Content-Type: application/yaml" \
  -H "X-API-Key: krn_your_api_key" \
  --data-binary @tenant-bundle.yaml

# 3. Apply for real (dryRun=false)
curl -X POST 'https://playground.kaireonai.com/api/v1/gitops/apply?dryRun=false' \
  -H "Content-Type: application/yaml" \
  -H "X-API-Key: krn_your_api_key" \
  --data-binary @tenant-bundle.yaml

How it works

Bundle format

A bundle is a manifest plus a flat resources[] array. Each resource follows a Kubernetes-like shape:
apiVersion: kaireonai.com/v1
kind: Offer
metadata:
  name: gold-card-application       # natural key for reconciliation
  annotations:
    kaireonai.com/version: "3"
    kaireonai.com/id: "..."         # optional DB id hint
spec:
  # entity-specific fields
The apiVersion value is always kaireonai.com/v1. Reconciliation matches by metadata.name, never by id, so the same bundle is promotable across environments.

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

dryRun
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.
prune
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.
kinds
string
Comma-separated kind filter — only the listed kinds are reconciled. Valid values:
Category, SubCategory, Offer, Creative, Channel,
QualificationRule, ContactPolicy, DecisionFlow, RankingProfile

Response

Body shape:
{
  "dryRun": false,
  "diffs": [
    {
      "kind": "Offer",
      "name": "gold-card-application",
      "op": "update",
      "changedFields": ["priority", "businessValue"]
    },
    {
      "kind": "Channel",
      "name": "email",
      "op": "unchanged"
    }
  ],
  "summary": {
    "created": 1,
    "updated": 1,
    "deleted": 0,
    "unchanged": 14
  }
}
dryRun
boolean
Mirrors the dryRun query parameter — true for a preview, false when changes were committed.
diffs
array
Per-resource diff. Each entry is { kind, name, op, changedFields?, before?, after? }. op is one of "create" | "update" | "delete" | "unchanged".
summary
object
Counts across all diffs: { created, updated, deleted, unchanged }.

Status codes

CodeWhen
200Apply or dry-run succeeded
400Invalid bundle (parse error, empty body, schema violation)
401Missing tenant context
403Invalid tenant identifier
500Reconciler 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

kinds
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

CodeWhen
200Diff computed
400Invalid bundle
401 / 403Missing or invalid tenant context
500Reconciler raised

GET /api/v1/gitops/export

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

Query parameters

format
string
default:"yaml"
yaml (default) or json.
layout
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:
manifest:
  apiVersion: kaireonai.com/v1
  kind: Bundle
  tenant: 5a9904b9-...
  exportedAt: "2026-04-30T14:00:00.000Z"
  resourceCounts:
    Offer: 12
    Channel: 4
    DecisionFlow: 1
resources:
  - apiVersion: kaireonai.com/v1
    kind: Offer
    metadata:
      name: gold-card-application
    spec:
      priority: 80
      businessValue: 100
      ...
Files layout — JSON response with a files map plus the original manifest:
{
  "files": {
    "bundle.yaml": "...",
    "offers/gold-card-application.yaml": "...",
    "channels/email.yaml": "..."
  },
  "manifest": { "apiVersion": "kaireonai.com/v1", "kind": "Bundle", ... }
}

Status codes

CodeWhen
200Export succeeded
401 / 403Missing or invalid tenant context
500Export raised — full message in error.message

Required headers

HeaderRequiredPurpose
Content-TypeYes (apply / diff)application/yaml, application/json, or any value containing yaml
X-API-KeyYes (one of the two)API key (krn_…)
X-Tenant-IdYes (one of the two)Direct tenant id

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:
cron:
  schedules:
    gitopsDriftCheck:
      enabled: true
      schedule: "0 5 * * *"
      path: "/api/v1/cron/gitops-drift-check"
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:
tenant/
├── bundle.yaml
├── categories/
├── channels/
├── decision-flows/
├── offers/
└── ranking-profiles/
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.