FF Solver

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

POST /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.

GET /schema

Returns the v2 JSON Schema for the /solve request body. Use this to validate problems client-side before sending.

GET /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

FieldTypeDefaultDescription
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

CategoryOperatorsExample
Arithmetic + - * / % x + y * 2
Comparison == != < <= > >= x + y <= 100
Unary - (negation) -x + y > 0
Grouping ( ) (x + y) * z < 50

Functions

FunctionDescriptionExample
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.

FieldTypeDescription
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.

FieldTypeDescription
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.

FieldTypeDescription
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.

FieldTypeDescription
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.

FieldTypeDescription
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.

FieldTypeDescription
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.

PriorityConditionSolver
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

StatusMeaning
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

TypeHTTP StatusDescription
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:

HeaderDescription
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)"
  }]
}