Docs

The NPCompleter HTTP API. Post a model, poll for the race result, reveal the solution. No API key.

NPCompleter is an HTTP API with a web page on top. Everything the site does, you can do with curl. There is no API key and no account. All requests and responses are JSON.

Post a problem

POST https://npcompleter.com/v1/problems
Content-Type: application/json

{"format": "cnf", "body": "p cnf 2 2\n1 2 0\n-1 0\n"}

Optional fields:

A 201 response:

{
  "id": "k3x9q2m7a1bc",
  "token": "a 32-character secret",
  "status": "queued",
  "url": "https://npcompleter.com/p/k3x9q2m7a1bc",
  "poll": "https://npcompleter.com/v1/problems/k3x9q2m7a1bc"
}

Keep the token. It is the only way to see the problem's body and solution. We store only its hash, so we cannot recover it or send it to you again.

To post a file, let jq do the JSON escaping. Read the file through stdin (jq -Rs); passing it as an argument fails for files over about 128 KB:

jq -Rs '{format: "lp", body: .}' model.lp \
  | curl -s https://npcompleter.com/v1/problems -H 'content-type: application/json' -d @-

Formats

formatWhat it isRacers
lpCPLEX LP file (linear and mixed-integer programs)HiGHS, SCIP
mpsMPS file, fixed or free (linear and mixed-integer programs)HiGHS, SCIP
cnfDIMACS CNF (Boolean satisfiability)Kissat, CaDiCaL
wcnfDIMACS WCNF (weighted MaxSAT)RC2
smt2SMT-LIB 2 script with (check-sat)Z3, cvc5
mznMiniZinc model, data inlinedCP-SAT
fznFlatZinc modelCP-SAT
cpsatOR-Tools CpModelProto, binary, base64-encodedCP-SAT
tsplibTSPLIB symmetric TSP instancePyVRP, OR-Tools routing
factorA positive integer up to 330 bits (demo)SymPy

The api parses every file before it stores anything. A file that does not parse gets a 400 with a message and, where it applies, a line number. Nothing is stored and it does not count against your daily limit. MiniZinc and FlatZinc get a shape check in the api and a full compile on the solver, so a type error there shows up as status: "invalid" a few seconds later.

Check a file without posting

POST https://npcompleter.com/v1/validate
{"format": "lp", "body": "..."}

200 {"size": {"vars": 3, "constraints": 3, "bytes": 212}} when it parses, or:

400 {"error": {"code": "invalid", "message": "line 14: unknown section \"Subject To:\" (expected \"Subject To\")", "line": 14}}

Validation stores nothing and has no rate limit.

Poll for the result

Request the poll URL every 2 seconds, and every 5 seconds once 30 seconds have passed, until status is solved, unsolved or invalid. A race takes at most 60 seconds once it starts. Send the owner token in the X-NPC-Token header, not in the URL, so it stays out of logs.

GET https://npcompleter.com/v1/problems/{id}
X-NPC-Token: {token}
curl -s https://npcompleter.com/v1/problems/{id} -H 'X-NPC-Token: {token}'

?token= in the URL still works for old links, but the header is the supported form.

The response is the problem's card:

{
  "id", "format", "size", "public", "status", "created_at", "attempt",
  "quality":  {"kind": "objective" | "answered" | "factored", "objective", "bound", "gap", "answered"} | null,
  "solve":    {"winner", "compute_ms", "racers": [{"name", "status", "wall_ms"}]} | null,
  "price":    {"usd", "free"} | null,
  "bounty_usd", "purged",
  "body",        with the token, or when public
  "solution",    with the token, when free or revealed
  "reveal_url"   with the token
}

status moves queued, then solving, then one of solved, unsolved or invalid. error explains invalid and unsolved: with the token it is the solver's full message; without it (and on the board) it is a short code such as invalid: parse error or unsolved: time cap, because solver messages can quote a private body. reason always holds that code.

Each racer's status is one of solved, infeasible, unbounded, timeout, stopped or error. stopped means the racer was cancelled because another racer proved its answer first; it is not a failure. timeout means the racer was still running at the 60-second cap.

Reveal a solution

If price.free is true, the solution is already in the response. Otherwise open reveal_url in a browser. Reveal is a web page on purpose: it asks a person for an email address and a confirmation, and it is not part of the API or the MCP tools.

Set a bounty

When a problem is queued, solving or unsolved, its owner can attach a bounty: the amount you would pay for a solution.

POST https://npcompleter.com/v1/problems/{id}/bounty
X-NPC-Token: {token}
{"amount_usd": 50, "email": "you@example.com"}

The response is {"bounty_usd": 50}, the running total. The bounty is recorded and nothing is charged.

The board

GET https://npcompleter.com/v1/problems?limit=50&cursor=...

Newest first. Public cards only: format, size, status, the race and the bounty. Bodies appear only for problems marked public.

Errors

Every error is {"error": {"code": "...", "message": "..."}}.

MCP

There is an MCP server at https://npcompleter.com/mcp (Streamable HTTP, no auth) with four tools: list_formats, solve, get_problem and set_bounty. solve waits up to 90 seconds for the race and returns the card and a reveal link. To add it to Claude Code:

claude mcp add --transport http npcompleter https://npcompleter.com/mcp

For agents

For a coding agent whose CP-SAT, MIP or SAT solve timed out or returned UNKNOWN. Export the model from the object you already have (model.ExportToFile("model.pb"), prob.writeLP("model.lp"), cnf.to_file("model.cnf")) and use one of the options below. Then show your human the reveal_url. Never promise an optimal answer: the race runs for at most 60 seconds.

curl, reading the file through stdin so large files work:

jq -Rs '{format: "lp", body: .}' model.lp | curl -s https://npcompleter.com/v1/problems -H 'content-type: application/json' -d @-

Python (pip install npcompleter), which takes the model object directly:

from npcompleter import Client
p = Client().post_cpsat(model)    # or post_pulp(prob), post_pysat(cnf)
print(p.wait().status, p.quality, p.reveal_url)

MCP, for Claude Code:

claude mcp add -t http npcompleter https://npcompleter.com/mcp

The Claude Code skill, which tells the agent when to use all of this:

claude plugin marketplace add ArthurRab/NPCompleter
claude plugin install npcompleter@npcompleter

Problems are private by default, private data is deleted 30 days after it was last viewed, and each body can be up to 1 MB.

Machine-readable