Constraint Solver

Kairos Constraint

High-performance constraint-satisfaction solver. Places entities on an N-dimensional grid or time axis, satisfies hard rules and minimises soft penalties.

Pricing

2,000Credits
Intake fee per solve

Once per request, regardless of duration or result.

+300per started solve-second
POST/v1/kairos/constraint

Solves a constraint satisfaction problem. The full solution is available via a presigned URL.

Parameter

NameTypeStatusDefaultDescription
dimensionsarray<Dimension>
Yes
—Variable axes (≥ 1). Each entry defines one dimension with a unique id — see Dimensions section.
entitiesarray<Entity>
Yes
—What gets placed (≥ 1). Each entity has min_placements / max_placements and an optional per-dimension restriction — see Entities section.
constraintsarray<Constraint>Optional[]Rules the solution must satisfy (hard) or pays penalty for violating (soft). 15 types available — see Constraints section.
configConfigOptional{}Solver settings: time budget, early stopping, correlation id, URL validity — see Config section.

Request

curl -X POST "https://api.centra.dbpg.io/v1/kairos/constraint" \
  -H "Authorization: Bearer sk-live-your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "dimensions": [
      { "type": "range", "id": "slot", "size": 6, "ordered": true },
      { "type": "set",   "id": "room", "values": ["main", "a", "b"] }
    ],
    "entities": [
      { "id": "keynote",
        "dimensions": { "slot": { "values": [0] }, "room": { "values": ["main"] } },
        "min_placements": 1, "max_placements": 1 },
      { "id": "talk_a",
        "dimensions": { "slot": {}, "room": { "values": ["a", "b"] } },
        "min_placements": 1, "max_placements": 1 }
    ],
    "constraints": [
      { "type": "not_same_slot", "hard": true,
        "entity_a": "talk_a", "entity_b": "keynote", "dimension": "slot" }
    ],
    "config": { "time_limit_seconds": 60, "request_id": "example-1" }
  }'

Response

{
  "status": "optimal",
  "request_id": "example-1",
  "warnings": [],
  "statistics": {
    "timing": { "model_build_seconds": 0.01, "solve_seconds": 0.03, "total_seconds": 0.04 },
    "problem_size": { "dimensions": 2, "entities": 2, "constraints": 1, "variables": 18, "cells": 18 },
    "total_penalty": 0, "best_bound": 0, "gap_percent": 0.0,
    "soft_constraint_violations": []
  },
  "result_url": "https://...s3.eu-central-1.amazonaws.com/csp/example-1.json?...",
  "result_expires_at": "2026-04-24T17:00:00+00:00",
  "usage": {
    "credits_used": 2300,
    "remaining_balance": 7700
  }
}

Dimensions

Each dimension is an axis over which entities take values. Three types. Every dimension needs a unique id within the request.

set — Categorical values

List of discrete named values. Good for rooms, teams, products, colours — anything where values have no inherent numerical order.

Parameter

NameTypeStatusDefaultDescription
type"set"
Yes
—Discriminator, must be exactly "set".
idstring
Yes
—Unique dimension identifier, referenced by entities and constraints.
valuesarray<string>
Yes
—Allowed string values. Order only matters when ordered=true.
orderedbooleanOptionalfalseDefault false. When true, values are treated as sorted — relevant for sequence, proximity, spacing.
{ "type": "set", "id": "room", "values": ["r1", "r2", "r3"], "ordered": false }

range — Numeric range

Integer values from 0 to size-1. Ideal for time slots, numbers, ring positions — anywhere ordering is semantically relevant.

Parameter

NameTypeStatusDefaultDescription
type"range"
Yes
—Discriminator, must be exactly "range".
idstring
Yes
—Unique dimension identifier.
sizeinteger ≥ 1
Yes
—Number of values. Allowed values are integers 0..size-1.
orderedbooleanOptionaltrueDefault true. range dimensions are ordered by default.
{ "type": "range", "id": "slot", "size": 40, "ordered": true }

interval — Continuous time axis

Time dimension with configurable duration. Internally the solver creates an IntervalVar with start/duration/end per entity; NoOverlap per resource group is automatic.

Parameter

NameTypeStatusDescription
type"interval"
Yes
Discriminator, must be exactly "interval".
idstring
Yes
Unique dimension identifier.
horizoninteger ≥ 1
Yes
Total length of the time axis in time units (e.g. 480 = 8 hours in minutes).
{ "type": "interval", "id": "time", "horizon": 480 }

Constraint: maximum one interval dimension per request.

Entities

The objects the solver places — lessons, ad slots, shifts, boxes, etc.

Fields

Parameter

NameTypeStatusDefaultDescription
idstring
Yes
—Unique entity identifier in the request. Referenced by constraints and the response.
namestringOptional""Free-text display label for UI and logs. Default empty string.
dimensionsobject
Yes
—Dict dim_id → EntityDimensionConfig. Each entry specifies which values this entity may use on the given dimension. Missing dimensions mean: all values allowed.
min_placementsinteger ≥ 1
Yes
—Minimum number of active placements for this entity (≥ 1).
max_placementsinteger ≥ 1
Yes
—Maximum number of active placements (≥ 1). Must be ≥ min_placements.

EntityDimensionConfig

Per dimension under entities[].dimensions. All fields optional.

Parameter

NameTypeStatusDefaultDescription
valuesarray<string|integer>Optional—Allowed values for this entity on this dimension, subset of the dimension values. Default: all values of the dimension.
mode"any" | "all"Optional"any""any" = entity picks one value per placement. "all" = entity occupies all listed values simultaneously as a block. Default "any".
sizeinteger ≥ 1Optional1Number of adjacent values a single placement occupies (≥ 1). Only meaningful on ordered dimensions — e.g. size=2 for a double period. Default 1.
durationinteger ≥ 1Optional1Only on interval dimensions. Duration of the placement in time units. Default 1.
{
  "id": "math_5a",
  "name": "Math 5a with Mueller",
  "dimensions": {
    "slot": {},
    "room": { "values": ["r1", "r2"], "mode": "any", "size": 1 }
  },
  "min_placements": 1,
  "max_placements": 1
}

Constraints

Rules applied to the solution. Each rule is either hard (must hold) or soft (may be violated at a penalty cost). The solver minimises the sum of all soft penalties.

Shared fields

All constraint types share these base fields; type-specific fields follow below.

Parameter

NameTypeStatusDefaultDescription
typestring
Yes
—Discriminator. One of the 15 types below.
idstringOptionalnullOptional identifier. Appears in response.statistics.soft_constraint_violations[].constraint_id when violated.
hardboolean
Yes
—true = must hold, otherwise infeasible. false = may be violated, costs penalty per violation unit.
penaltyinteger ≥ 1Alternative—Required when hard=false. Cost per violation unit (integer ≥ 1).

Overview

All 15 rule types at a glance. The type column links to the detail below.

#TypeModeSummaryRequired fields
1fill
hardsoft
Every cell must be occupied.—
2cardinality
hardsoft
Bounds the placement count of an entity.
entityminmax
3proximity
hardsoft
Minimum gap between two entities on one dimension.
entity_aentity_bdimensionmin_gap
4spacing
soft
Anti-clump — penalises adjacent placements of the same entity.
entitydimensionclump_penalty
5same_slot
hardsoft
Two entities must share a dimension value.
entity_aentity_bdimension
6not_same_slot
hardsoft
Two entities must not share a dimension value.
entity_aentity_bdimension
7sequence
hardsoft
Entity A must come before entity B.
firstthendimension
8follows
hardsoft
Entity B must be placed directly after entity A.
entity_aentity_bdimension
9not_follows
hardsoft
Entity B must not be placed directly after entity A.
entity_aentity_bdimension
10mutual_exclusion
hardsoft
Only one of the listed entities per value.
entitiesdimension
11max_per_value
hardsoft
Upper bound on placements of an entity per value.
entitydimensionmax
12cumulative
hardsoft
Weighted capacity bound per value.
dimensiondemandsmax_capacity
13pairwise_exclusion
hardsoft
At most one entity per combination of several dimensions.
dimensions
14pairwise_fill
hardsoft
Every combination of several dimensions must be occupied.
dimensions
15expression
hardsoft
Free-form rule in a custom DSL.
rule

Detailed type reference

fill

Every cell (combination of all dimension values) must be occupied by at least one entity. Classic full-coverage of a grid.

hardsoft

No type-specific fields — only the shared fields above.

Example

{ "type": "fill", "hard": true }

cardinality

Limits how often an entity may be placed. For soft: each missing or surplus placement costs penalty.

hardsoft

Type-specific fields

NameTypeStatusDescription
entitystring
Yes
ID of the entity to bound.
mininteger
Yes
Minimum number of placements.
maxinteger
Yes
Maximum number of placements.

Example

{
  "type": "cardinality",
  "hard": false,
  "penalty": 5,
  "entity": "math_5a",
  "min": 4,
  "max": 5
}

proximity

Two entities must be at least min_gap values apart on the given (ordered) dimension.

hardsoft

Type-specific fields

NameTypeStatusDescription
entity_astring
Yes
ID of the first entity.
entity_bstring
Yes
ID of the second entity.
dimensionstring
Yes
Dimension the gap is measured on. Must be ordered.
min_gapinteger ≥ 1
Yes
Minimum gap in value steps.

Example

{
  "type": "proximity",
  "hard": true,
  "entity_a": "brand_A",
  "entity_b": "brand_B",
  "dimension": "slot",
  "min_gap": 3
}

spacing

Anti-clump rule: prevents placements of the same entity from sitting in directly adjacent values. Always soft — hard=false is mandatory.

soft

Type-specific fields

NameTypeStatusDescription
entitystring
Yes
ID of the entity whose placements should be spread.
dimensionstring (ordered)
Yes
Ordered dimension on which adjacency is checked.
clump_penaltyinteger ≥ 1
Yes
Penalty per directly-adjacent pair of the same entity.

Example

{
  "type": "spacing",
  "hard": false,
  "penalty": 10,
  "entity": "lightning",
  "dimension": "slot",
  "clump_penalty": 50
}

same_slot

When entity A is active, at least one placement of B must share the same dimension value. Couples two entities.

hardsoft

Type-specific fields

NameTypeStatusDescription
entity_astring
Yes
ID of the first entity (triggers the coupling).
entity_bstring
Yes
ID of the second entity (must match the value).
dimensionstring
Yes
Dimension on which the value equality holds.

Example

{
  "type": "same_slot",
  "hard": true,
  "entity_a": "a",
  "entity_b": "b",
  "dimension": "day"
}

not_same_slot

Entity A and entity B must not share a dimension value. Decouples two entities.

hardsoft

Type-specific fields

NameTypeStatusDescription
entity_astring
Yes
ID of the first entity.
entity_bstring
Yes
ID of the second entity.
dimensionstring
Yes
Dimension on which values must differ.

Example

{
  "type": "not_same_slot",
  "hard": true,
  "entity_a": "a",
  "entity_b": "b",
  "dimension": "day"
}

sequence

The earliest active placement of first must come before the earliest of then. For ordering dependencies.

hardsoft

Type-specific fields

NameTypeStatusDescription
firststring
Yes
ID of the entity that must come first.
thenstring
Yes
ID of the entity that comes after.
dimensionstring (ordered)
Yes
Ordered dimension on which order is defined.

Example

{
  "type": "sequence",
  "hard": true,
  "first": "intro",
  "then": "advanced",
  "dimension": "slot"
}

follows

When entity A is active at position t, entity B must be active at position t+1. Direct temporal successor.

hardsoft

Type-specific fields

NameTypeStatusDescription
entity_astring
Yes
ID of the predecessor entity.
entity_bstring
Yes
ID of the entity that must come immediately after.
dimensionstring (ordered)
Yes
Ordered dimension on which succession is defined.

Example

{
  "type": "follows",
  "hard": true,
  "entity_a": "a",
  "entity_b": "b",
  "dimension": "slot"
}

not_follows

When entity A is at position t, entity B must not be at t+1. Negative form of follows.

hardsoft

Type-specific fields

NameTypeStatusDescription
entity_astring
Yes
ID of the predecessor entity.
entity_bstring
Yes
ID of the entity that must not come immediately after.
dimensionstring (ordered)
Yes
Ordered dimension on which succession is checked.

Example

{
  "type": "not_follows",
  "hard": true,
  "entity_a": "a",
  "entity_b": "b",
  "dimension": "slot"
}

mutual_exclusion

At most one entity from the listed group may be active per dimension value.

hardsoft

Type-specific fields

NameTypeStatusDescription
entitiesarray<string>
Yes
List of entity IDs that mutually exclude each other.
dimensionstring
Yes
Dimension on which the exclusivity applies.

Example

{
  "type": "mutual_exclusion",
  "hard": true,
  "entities": ["a", "b", "c"],
  "dimension": "slot"
}

max_per_value

Upper bound on how often a single entity may be placed per dimension value.

hardsoft

Type-specific fields

NameTypeStatusDescription
entitystring
Yes
ID of the entity affected.
dimensionstring
Yes
Dimension on which the bound applies.
maxinteger ≥ 1
Yes
Maximum number of placements per value (≥ 1).

Example

{
  "type": "max_per_value",
  "hard": true,
  "entity": "brand_x",
  "dimension": "slot",
  "max": 1
}

cumulative

Weighted capacity constraint: the sum of weights of all entities active on any dimension value must not exceed max_capacity. Classic capacitated-resource rule.

hardsoft

Type-specific fields

NameTypeStatusDescription
dimensionstring
Yes
Dimension on which capacity is measured.
demandsobject<entity_id, integer>
Yes
Dict entity_id → weight. Entities not listed have weight 0.
max_capacityinteger ≥ 1
Yes
Capacity bound per dimension value (≥ 1).

Example

{
  "type": "cumulative",
  "hard": true,
  "dimension": "slot",
  "demands": {
    "truck_a": 3,
    "truck_b": 2,
    "truck_c": 4
  },
  "max_capacity": 6
}

pairwise_exclusion

For every combination of the listed dimension values, at most one entity may be active. Generalises cell exclusivity to any dimension subset. Classic: "a teacher can never be in two rooms at the same time" via dimensions=[slot, teacher].

hardsoft

Type-specific fields

NameTypeStatusDescription
dimensionsarray<string> (≥ 1)
Yes
List of dimension ids whose combinations are exclusive (≥ 1 element).

Example

{
  "type": "pairwise_exclusion",
  "hard": true,
  "dimensions": ["slot", "teacher"]
}

pairwise_fill

For every combination of the listed dimension values, at least one entity must be active. Generalises fill to any dimension subset.

hardsoft

Type-specific fields

NameTypeStatusDescription
dimensionsarray<string> (≥ 1)
Yes
List of dimension ids whose combinations must be covered (≥ 1 element).

Example

{
  "type": "pairwise_fill",
  "hard": true,
  "dimensions": ["slot", "room"]
}

expression

Free-form rule in a client-side DSL for special cases. Not content-validated by the verifier — only evaluated by the solver. Only use when none of the other 14 types fit.

hardsoft

Type-specific fields

NameTypeStatusDescription
rulestring (DSL)
Yes
Rule string in custom syntax.

Example

{
  "type": "expression",
  "hard": true,
  "rule": "<custom DSL>"
}

Config

All config fields are optional; schema defaults apply.

Fields

Parameter

NameTypeStatusDefaultDescription
time_limit_secondsfloat (1–600)Optional60Maximum time the solver may spend searching. Between 1 and 600 seconds. Default 60.
stop_after_first_solutionbooleanOptionalfalsetrue = solver returns the first feasible solution and exits. No optimisation beyond that. Default false.
relative_gap_limitfloat (0–1) | nullOptionalnullStops the solver as soon as the relative gap to the theoretical lower bound is ≤ X. E.g. 0.05 = 5 %. Default null.
absolute_gap_limitinteger ≥ 0 | nullOptionalnullStops the solver as soon as the absolute penalty gap is ≤ X. Default null.
request_idstring | nullOptionalnullYour own correlation id. Echoed 1:1 into the response and appears in CloudWatch logs for cross-system tracing.
result_url_expires_ininteger (60–604800)Optional10800Validity of the presigned URL in seconds. Between 60 and 604800 (7 days). Default 10800 (3 h).
hintsarray<HintAssignment>Optional[]Warm-start hints from a previous solution. Each entry has the fields entity and values.

Response

For status optimal and feasible the full solution lives behind result_url. The wire-body fields:

Status values

HTTP status code and billing logic per result classification.

statusHTTPBilledMeaning
optimal200Yesprovably optimal solution
feasible200Yesvalid solution, optimality not proven (time limit hit or early stop)
infeasible200Yesprovably no solution exists — diagnostics block has details
unknown200Yestime-out without a solution and without proving infeasibility
invalid400Norequest schema ok but logically invalid (e.g. min > max)

assignments

List of all placements. Only in the S3 object, not in the wire body. For status infeasible / unknown / invalid the block is missing entirely.

Parameter

NameTypeStatusDescription
entitystring
Yes
ID of the placed entity.
placementinteger
Yes
Placement ordinal for this entity (1..max_placements).
valuesobject
Yes
Concrete values per dimension for this placement. On interval dimensions the object additionally contains a key suffixed with _end for the end time.

statistics

Performance and quality metrics. Always present in the wire body (also for offloaded responses).

Parameter

NameTypeStatusDescription
timing.model_build_secondsfloat
Yes
Time to build variables and constraints before the actual solve.
timing.solve_secondsfloat
Yes
Pure solver time.
timing.total_secondsfloat
Yes
End-to-end inside the solver (model_build + solve). Billing basis.
problem_size.dimensionsinteger
Yes
Number of dimensions in the request.
problem_size.entitiesinteger
Yes
Number of entities in the request.
problem_size.constraintsinteger
Yes
Number of constraints in the request.
problem_size.variablesinteger
Yes
Number of internal solver variables — indicator of problem size.
problem_size.cellsinteger
Yes
Number of cells (combinations of all dimension values).
placements_per_entityobject
Yes
How often each entity ended up placed in the solution.
total_penaltyinteger
Yes
Sum of all soft-violation costs in the solution. 0 = no soft violations.
best_boundinteger
Yes
Solver-proven lower bound on the penalty. When total_penalty == best_bound the solution is provably optimal.
gap_percentfloat
Yes
(total_penalty - best_bound) / total_penalty × 100. 0 = optimal.
soft_constraint_violationsarray
Yes
One entry per violated soft constraint with constraint_id, type, violation_count, penalty_total.

diagnostics

Present only for status infeasible or unknown. Helps debug why the solver couldn't find a solution.

Parameter

NameTypeStatusDescription
messagestring
Yes
Human-readable summary of the problem.
capacity_conflictsarrayOptionalList of dimension values where demand exceeds capacity. Each entry: dimension, value, min_demand, available_slots, entities.
conflicting_constraintsarrayOptionalList of constraints that make the problem infeasible. Each entry: index, type, description.