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:
public:trueto make the body visible on the board. Defaultfalse. It does nothing else yet. Later it will let other solvers compete your price down.source_ref: a short tag of your own, such as the name of the script that posted it.session: an id of your own, up to 128 characters, that groups your requests in our usage counts. The bounty request takes it too. AnX-NPC-Sessionheader does the same on any request, GETs included; when both are sent, the body field wins.
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
| format | What it is | Racers |
|---|---|---|
lp | CPLEX LP file (linear and mixed-integer programs) | HiGHS, SCIP |
mps | MPS file, fixed or free (linear and mixed-integer programs) | HiGHS, SCIP |
cnf | DIMACS CNF (Boolean satisfiability) | Kissat, CaDiCaL |
wcnf | DIMACS WCNF (weighted MaxSAT) | RC2 |
smt2 | SMT-LIB 2 script with (check-sat) | Z3, cvc5 |
mzn | MiniZinc model, data inlined | CP-SAT |
fzn | FlatZinc model | CP-SAT |
cpsat | OR-Tools CpModelProto, binary, base64-encoded | CP-SAT |
tsplib | TSPLIB symmetric TSP instance | PyVRP, OR-Tools routing |
factor | A 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": "..."}}.
400: the file did not parse (code: "invalid", withlinewhen known), or the format is unknown.413: the body is over 1 MB (1,000,000 bytes).429: more than 20 posts today from your IP address. Validation is not counted.
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
https://npcompleter.com/v1/formats: formats, limits and racers.https://npcompleter.com/openapi.json: this API as OpenAPI.https://npcompleter.com/llms.txt: a short index for language models.- Every page on this site has a Markdown version at the same path with
.mdadded, for examplehttps://npcompleter.com/docs.md. A problem's page,https://npcompleter.com/p/{id}.md, gives its card as Markdown: format, size, status, the race, the price and how to reveal.