Documentation
API Reference
Solve constraint satisfaction and optimization problems with a single POST request. Define variables, constraints, and an optional objective — all in JSON.
Quick Start
Send a JSON problem to POST /solve and get back variable assignments.
No API key required for the demo tier.
Request
curl -X POST https://solver.fastflowtech.ai/solve \
-H "Content-Type: application/json" \
-d '{
"variables": [
{"name": "x", "type": "int",
"domain": {"min": 0, "max": 10}},
{"name": "y", "type": "int",
"domain": {"min": 0, "max": 10}}
],
"constraints": [
{"type": "expression",
"expression": "x + y <= 15"},
{"type": "expression",
"expression": "x > y"}
],
"objective": {
"target": "x", "goal": "maximize"
}
}'
Response
{
"status": "optimal",
"solution": {
"x": 10,
"y": 5
},
"stats": {
"solver": "scip",
"solve_time_us": 412,
"routing_reason": "Pure linear arithmetic"
}
}
Authentication
Requests without authentication use the demo tier (50 variables, 2s timeout, 10 req/min). To unlock higher limits, include an API key or JWT token.
API Key
After subscribing, you'll receive an API key starting with ak_solver-.
Include it in the Authorization header:
curl -X POST https://solver.fastflowtech.ai/solve \
-H "Authorization: Bearer ak_solver-your-key-here" \
-H "Content-Type: application/json" \
-d '{ ... }'
Alternatively, use the X-Api-Key header: X-Api-Key: ak_solver-your-key-here
JWT (FF Auth)
If your application uses FF Auth, pass the JWT token directly:
curl -X POST https://solver.fastflowtech.ai/solve \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{ ... }'
JWTs are validated against the FF Auth JWKS endpoint. The tier is determined by the organization's subscription.
Endpoints
/solve
Solve a constraint satisfaction or optimization problem. Accepts a JSON problem definition and returns variable assignments, status, and solver statistics. See Request Format and Response Format for details.
/schema
Returns the v2 JSON Schema for the /solve request body.
Use this to validate problems client-side before sending.
/health
Health check endpoint. Returns {"status": "ok"}.
Request Format
The request body is a JSON object with variables, constraints,
and optional objective and settings fields.
{
"version": 2, // optional, defaults to 2
"variables": [ ... ], // required
"constraints": [ ... ], // required
"objective": { ... }, // optional
"settings": { ... } // optional
}
Variables
Each variable has a name, type, and type-specific fields.
Names must match ^[a-zA-Z_]\w*$.
Integer variables
Use domain.min/domain.max for a contiguous range, or domain.intervals
for disjoint ranges.
Range domain
{
"name": "x",
"type": "int",
"domain": { "min": 0, "max": 100 }
}
Interval domain (disjoint)
{
"name": "shift",
"type": "int",
"domain": {
"intervals": [[0,10], [20,30]]
}
}
Boolean variables
{ "name": "active", "type": "bool" }
Enum variables
Enums are encoded as integers internally (0-indexed). You can reference values by label in expressions
using string syntax: hero == 'dense'.
{
"name": "hero",
"type": "enum",
"values": ["minimal", "dense", "video"]
}
Objective
Optional. Specify a variable to maximize or minimize. Without an objective, the solver finds any feasible assignment (satisfaction mode).
{
"target": "profit",
"goal": "maximize" // or "minimize"
}
Settings
| Field | Type | Default | Description |
|---|---|---|---|
| timeout_ms | number | 1000 | Solver timeout in milliseconds. Must be > 0 and within your tier limit. |
| solver_hint | string | null | null | Override automatic solver routing. Values: "scip", "cpsat", "z3", or null for auto. |
Expression Language
Expressions are strings used in expression and implication constraints.
They support arithmetic, comparisons, and built-in functions.
Operators
| Category | Operators | Example |
|---|---|---|
| Arithmetic | + - * / % |
x + y * 2 |
| Comparison | == != < <= > >= |
x + y <= 100 |
| Unary | - (negation) |
-x + y > 0 |
| Grouping | ( ) |
(x + y) * z < 50 |
Functions
| Function | Description | Example |
|---|---|---|
abs(x) |
Absolute value | abs(x - y) <= 3 |
min(a, b) |
Minimum of two values | min(x, y) >= 5 |
max(a, b) |
Maximum of two values | max(x, y) <= 20 |
sum(...) |
Sum of arguments | sum(a, b, c) == 100 |
count(...) |
Count of arguments | count(a, b, c) > 1 |
Enum Labels
Reference enum values by label using single-quoted strings. The label is resolved to its index before solving.
// Variable: hero with values ["minimal", "dense", "video"]
"hero == 'dense'" // resolved to: hero == 1
"hero != 'minimal'" // resolved to: hero != 0
Nonlinear note: Expressions with *, /, or %
between two variables (e.g. x * y) are nonlinear and will be routed to Z3.
Multiplying a variable by a constant (x * 3) is linear and uses SCIP.
Constraint Types
Each constraint in the constraints array must have a type field.
Six types are available:
expression
An arithmetic or comparison expression using the expression language.
| Field | Type | Description | |
|---|---|---|---|
| type | string | required | "expression" |
| expression | string | required | The expression string (see Expression Language) |
{ "type": "expression", "expression": "x + y <= 10" }
all_different
All listed variables must take distinct values.
| Field | Type | Description | |
|---|---|---|---|
| type | string | required | "all_different" |
| vars | string[] | required | Variable names (minimum 2) |
{ "type": "all_different", "vars": ["a", "b", "c"] }
implication
If the if condition holds, then the then condition must also hold.
| Field | Type | Description | |
|---|---|---|---|
| type | string | required | "implication" |
| if | string | required | Condition expression |
| then | string | required | Consequent expression |
{
"type": "implication",
"if": "hero == 'dense'",
"then": "density > 5"
}
cardinality
Constrain how many of the listed variables equal a given value.
| Field | Type | Description | |
|---|---|---|---|
| type | string | required | "cardinality" |
| mode | string | required | "at_most", "at_least", or "exactly" |
| vars | string[] | required | Variable names |
| count | number | required | How many variables must match |
| value | number | optional | Value to compare against (default: 1) |
// At most 2 of these variables can equal 1
{
"type": "cardinality",
"mode": "at_most",
"vars": ["a", "b", "c", "d"],
"count": 2,
"value": 1
}
any_of
Disjunction — at least one of the nested constraints must hold.
| Field | Type | Description | |
|---|---|---|---|
| type | string | required | "any_of" |
| constraints | constraint[] | required | Nested constraint objects |
{
"type": "any_of",
"constraints": [
{ "type": "expression", "expression": "x <= 5" },
{ "type": "expression", "expression": "y >= 10" }
]
}
table
Restrict variable combinations to allowed or forbidden tuples.
| Field | Type | Description | |
|---|---|---|---|
| type | string | required | "table" |
| mode | string | required | "allowed" or "forbidden" |
| vars | string[] | required | Variable names (order matches tuple positions) |
| tuples | number[][] | required | List of value tuples |
// Only these (x, y) combinations are allowed
{
"type": "table",
"mode": "allowed",
"vars": ["x", "y"],
"tuples": [[0,1], [1,0], [2,2]]
}
Solver Routing
Problems are automatically analyzed and routed to the best solver engine.
You can override this with settings.solver_hint.
| Priority | Condition | Solver |
|---|---|---|
| 1 | Nonlinear expressions (var * var, var / var, var % var) |
Z3 |
| 2 | Interval (disjoint) domains | CP-SAT or Z3 |
| 3 | Function calls (abs, min, max, sum, count) |
CP-SAT |
| 4 | Global constraints (all_different, implication, cardinality, any_of, table) |
CP-SAT |
| 5 | != comparisons |
CP-SAT |
| 6 | Pure linear arithmetic (default) | SCIP |
SCIP
Linear programming solver. Fastest for pure linear arithmetic with <, <=, >, >=, == constraints. Sub-millisecond solves on typical problems.
CP-SAT
Constraint programming solver. Handles global constraints, functions, disjoint domains, and !=. Good all-around performance.
Z3
SMT solver. Required for nonlinear expressions (variable-to-variable multiplication, division, modulo). Most flexible but slowest.
Response Format
{
"status": "optimal",
"solution": {
"x": 10,
"y": 5,
"hero": "dense",
"active": true
},
"stats": {
"solver": "cpsat",
"solve_time_us": 1234.5,
"total_time_us": 2500.1,
"num_variables": 10,
"num_constraints": 25,
"routing_reason": "Global constraints detected: all_different",
"preprocessing": {
"variables_fixed": 2,
"domains_tightened": 3,
"symmetries_broken": 1,
"constraints_removed": 0,
"iterations": 2
}
}
}
Status Values
| Status | Meaning |
|---|---|
optimal |
Objective was provided and the best possible solution was found. |
satisfied |
No objective — a feasible assignment was found. |
feasible |
Objective was provided — a valid solution was found but optimality is not proven (timeout hit). |
infeasible |
No solution exists that satisfies all constraints. |
timeout |
Solver ran out of time without finding any solution. |
error |
Validation or server error — see errors array. |
Tier Limits
Each tier defines resource limits enforced per request. Exceeding a limit returns
a resource_limit error.
| Limit | Demo (free) | Starter ($29) | Pro ($99) | Enterprise ($499) |
|---|---|---|---|---|
| Variables | 50 | 200 | 1,000 | 10,000 |
| Constraints | 100 | 500 | 2,000 | 20,000 |
| Expression length | 256 | 512 | 1,024 | 4,096 |
| Timeout | 2s | 5s | 30s | 120s |
| Memory | 128 MB | 256 MB | 512 MB | 2,048 MB |
| Rate limit | 10/min | 30/min | 120/min | 1,000/min |
Error Handling
Errors are returned with "status": "error" and an errors array.
Each error has a type, field, and message.
{
"status": "error",
"solution": null,
"stats": null,
"errors": [
{
"type": "validation",
"field": "constraints[0].expression",
"message": "unknown variable: z"
}
]
}
Error Types
| Type | HTTP Status | Description |
|---|---|---|
validation |
200 | Invalid request structure, unknown variables, bad expressions |
resource_limit |
200 / 413 | Exceeded tier limits (variables, constraints, expression length, timeout). Body size returns 413. |
rate_limit |
429 | Too many requests — see Rate Limiting |
error |
200 | Internal solver error |
Rate Limiting
Rate limits are enforced per organization (or per anonymous client) using a 60-second sliding window.
All /solve responses include rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Maximum requests allowed per minute for your tier |
X-RateLimit-Remaining |
Requests remaining in the current window |
X-RateLimit-Reset |
Unix timestamp when the window resets |
When the rate limit is exceeded, the API returns HTTP 429:
{
"status": "error",
"errors": [{
"type": "rate_limit",
"field": "request",
"message": "rate limit exceeded (30/min)"
}]
}