> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usehasp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Constraint Solving

> POST /v1/solve — deterministic assignment/scheduling as a direct API call.

`POST /v1/solve` runs a declarative constraint-satisfaction / assignment spec (staff scheduling, coverage rostering, resource allocation) through HASP's self-hosted CP-SAT solver and returns the computed assignment. It is the same deterministic solving capability behind the `solve` [Studio workflow step](/app-builder) and the `solve.run` agent tool — the request body **is** the spec (`version`, `resources`, `slots`, `constraints`, `preferences`, `options`), sent directly to the solver with no reimplementation of its validation.

For the full constraint vocabulary (`HARD_TYPES`/`SOFT_TYPES`, size and magnitude bounds) and worked spec examples, see [`apps/solver/README.md`](https://github.com/Charity-Wages/Hasp-monorepo/blob/staging/apps/solver/README.md) — this page does not restate it.

## Literal specs only

Unlike the workflow step and the `solve.run` agent tool, `POST /v1/solve` does **not** accept `resources_from`/`slots_from` — sending either is rejected with a `422 VALIDATION_FAILED`. An integrator calling this endpoint already holds its own resource/slot data and is sending it directly; the workflow step and the agent tool are the surfaces that source `resources`/`slots` from a project's live HASP records instead.

## Infeasible is a 200

`optimal`, `feasible`, and `infeasible` are all successful outcomes — every one returns **HTTP 200**. On `infeasible`, `data.assignments` is empty and `data.conflicts` names the subset of your declared constraints that cannot be simultaneously satisfied, in your spec's own vocabulary (constraint/slot ids). A `4xx`/`5xx` on an infeasible solve would be a bug — treat `infeasible` as a normal, actionable result, not an error to catch.

## Request

```json theme={null}
{
  "version": 1,
  "resources": [
    { "id": "Riley Nguyen", "attributes": { "role": "rn" } },
    { "id": "Casey Brooks", "attributes": { "role": "ma" } }
  ],
  "slots": [
    { "id": "2026-08-03-day", "day": "2026-08-03", "requirements": [{ "count": 1, "where": [{ "key": "role", "equals": "rn" }] }] }
  ],
  "constraints": [
    { "type": "max_per_day", "resource_ids": ["Riley Nguyen", "Casey Brooks"], "limit": 1 }
  ],
  "preferences": [],
  "options": { "time_limit_seconds": 10 }
}
```

`options.time_limit_seconds` is capped at **30** (`422` above that); omit it to use the engine's 10-second default.

## Response — optimal

```json theme={null}
{
  "data": {
    "object": "solve_result",
    "status": "optimal",
    "assignments": [
      { "slot_id": "2026-08-03-day", "resource_ids": ["Riley Nguyen"] }
    ],
    "objective": 0,
    "conflicts": [],
    "stats": { "wall_time_ms": 42, "seed": 0 },
    "engine": "cp-sat"
  }
}
```

## Response — infeasible

```json theme={null}
{
  "data": {
    "object": "solve_result",
    "status": "infeasible",
    "assignments": [],
    "objective": null,
    "conflicts": [
      { "id": "coverage:2026-08-03-day", "description": "No resource satisfies the requirement for slot 2026-08-03-day." }
    ],
    "stats": { "wall_time_ms": 18, "seed": 0 },
    "engine": "cp-sat"
  }
}
```

## Auth and billing

Requires the `workflow.run` capability (granted by the `control:workflows` PAT scope — the same scope that grants triggering a workflow run, since a direct solve is the same class of metered compute action). Delegated agent credentials cannot currently call this endpoint; an agent solves through the `solve.run` tool instead. Each call meters one `agent_actions` action (and, on the API surface, one `api_agent_actions` action) — see [Rate Limits](/ai-api/reference/rate-limits) for the org-level RPM/daily buckets this endpoint also draws from.

## Auditing

Every call — `optimal`, `feasible`, or `infeasible` — records a `workflow.solve_completed` audit event carrying the solver status, conflict ids, and the sha256 of the exact spec sent to the solver. Because solving is deterministic, the hash makes any recorded solve independently reproducible without the spec itself (which may carry workforce identifiers) living in the audit row.

## Error codes

| Code                  | Description                                                                                                          |
| --------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `SOLVER_SPEC_INVALID` | The solver rejected the spec against its closed constraint vocabulary. `error.details.errors` carries the full list. |
| `SOLVER_UNAVAILABLE`  | The solver sidecar is unreachable or misconfigured. Retry with backoff.                                              |

See [Errors](/ai-api/reference/errors) for the full error envelope shape.
