De NexOR Optimization API
Dien een probleem in, krijg een oplossing. Eén REST-call erin, één antwoord eruit, stabiel onder /solve/v1 en geauthenticeerd met een bearer-sleutel. Elk endpoint op deze pagina draait vandaag; de Python-SDK is een preview en staat als zodanig aangeduid.
Free tier, geen kaart nodig. Uw eerste solve is een kwestie van minuten.
-
POST
/problems -
queuedhet probleem heeft een id -
POST
/problems/{id}/wait -
runninghier blijft de wait-call openstaan -
finishedde wait keert terug, of uw webhook gaat af -
GET
/problems/{id}/result -
solutionu hebt het antwoord
Een solver die u over HTTP aanroept
U stuurt een wiskundig optimalisatieprobleem als JSON. Wij draaien het op onze solvers en geven de oplossing terug. Het is domeinonafhankelijk: lineaire programma's, gemengd geheeltallige modellen, routering, scheduling, wat u ook kunt uitdrukken, alles reist in dezelfde envelope.
Elke integratie kent dezelfde drie bewegingen: verstuur een probleem en krijg een id, wacht op één aanvraag of laat een ondertekende webhook u bereiken, en haal daarna de oplossing op. De volgende sectie doet alle drie in minder dan een minuut.
Wij behandelen de body van uw probleem als opaak. De manager valideert de envelope en meet het rekenverbruik, maar leest uw model nooit. Dat is wat één API generiek houdt voor elke probleemklasse.
Uw eerste solve, in drie stappen
Van nul naar een echt antwoord in enkele minuten. Een productiemix met twee producten: maximaliseer de marge binnen limieten op machine-uren en materiaal. Het optimum is 30 stoelen en 5 tafels, doelfunctiewaarde 1750.
Test het eerst live
Geen account, geen sleutel, gratis. Schrijf een model in Python en laat het lopen op de echte solver, in uw browser.
-
Kopieer een API-sleutel
Geef hem een naam en kies een vervaldatum, meer stelt het formulier niet voor. Deze sleutel identificeert u bij elke API-call die u doet. Het geheim wordt maar één keer getoond, kopieer het dus meteen. Dit is de enige stap die in de browser gebeurt.
API-sleutels openen -
Indienen, wachten, ophalen
Plak uw sleutel in het voorbeeld hieronder en voer het uit: dien de envelope in, houd één aanvraag open tot het probleem klaar is, en haal dan de oplossing op. Dat is de kortste weg vanaf een laptop, waar niets een webhook kan ontvangen. Zodra u een server draait, registreer een webhook en laat de middelste stap vallen.
# Set these once. BASE is your NexOR host; the key comes from your portal.
export BASE="https://<your-nexor-host>/solve/v1"
export API_KEY="<your-api-key>"
# 1. Submit the envelope (saved as envelope.json).
curl -s -X POST "$BASE/problems" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d @envelope.json
# -> {"problem_id":"prb_9f3c...","status":"queued","time_limit_seconds":5,...}
# 2. Wait. The request stays open until the problem ends, and timeout_seconds
# is capped at 60, so repeat the call while the status is queued or running.
# A server registers a webhook instead and skips this step altogether.
curl -s -X POST "$BASE/problems/prb_9f3c.../wait?timeout_seconds=60" \
-H "Authorization: Bearer $API_KEY"
# -> the snapshot, with "outcome" set once the solve has ended
# 3. Fetch the solution. Without inline=1 the route answers 302 to a presigned
# object URL, so a plain curl prints nothing; pass -L to follow it instead.
curl -s "$BASE/problems/prb_9f3c.../result?inline=1" \
-H "Authorization: Bearer $API_KEY"
# -> {"problem_id":"prb_9f3c...","outcome":"solved","solution":{
# "outcome":"solved","termination_status":"OPTIMAL","objective_value":1750.0,
# "result":{"values":{"chairs":30.0,"tables":5.0}}}}
import os, time, requests
BASE = os.environ["SOLVE_BASE"] # https://<your-nexor-host>/solve/v1
HEAD = {"Authorization": f"Bearer {os.environ['SOLVE_API_KEY']}"}
envelope = {
"api_version": "1",
"problem": {
"variables": [{"name": "chairs", "lower": 0}, {"name": "tables", "lower": 0}],
"constraints": [
{"expr": "4*chairs + 8*tables <= 160", "name": "machine_hours"},
{"expr": "2*chairs + 3*tables <= 75", "name": "material"},
],
"objective": {"sense": "max", "expr": "45*chairs + 80*tables"},
},
"solver": "highs",
"options": {"compute_preset": "cpu-standard", "time_limit_seconds": 5},
}
# 1. Submit.
prb = requests.post(f"{BASE}/problems", json=envelope, headers=HEAD).json()
print("submitted", prb["problem_id"], prb["status"])
# 2. Wait. The request is held open until the problem ends; the 60 second cap
# means a longer solve answers a snapshot that is not terminal yet, so the
# call is repeated. For live progress open GET /problems/{id}/events instead.
RUNNING = {"queued", "running"}
while True:
reply = requests.post(
f"{BASE}/problems/{prb['problem_id']}/wait",
params={"timeout_seconds": 60},
headers=HEAD,
)
if reply.status_code == 429: # asked to slow down
time.sleep(int(reply.headers.get("Retry-After", 5)))
continue
reply.raise_for_status()
state = reply.json()
if state["status"] not in RUNNING:
break
# 3. Fetch the solution.
reply = requests.get(f"{BASE}/problems/{prb['problem_id']}/result", headers=HEAD)
while reply.status_code == 429: # a burst of submits spent the budget
time.sleep(int(reply.headers.get("Retry-After", 5)))
reply = requests.get(f"{BASE}/problems/{prb['problem_id']}/result", headers=HEAD)
reply.raise_for_status() # 410 once past retention
sol = reply.json()
print("outcome:", sol["outcome"]) # "solved"
print("objective:", sol["solution"]["objective_value"]) # 1750.0
print("values:", sol["solution"]["result"]["values"]) # {"chairs": 30.0, ...}
De envelope.json die hierboven gebruikt wordt
{
"api_version": "1",
"problem": {
"variables": [
{
"name": "chairs",
"lower": 0
},
{
"name": "tables",
"lower": 0
}
],
"constraints": [
{
"expr": "4*chairs + 8*tables <= 160",
"name": "machine_hours"
},
{
"expr": "2*chairs + 3*tables <= 75",
"name": "material"
}
],
"objective": {
"sense": "max",
"expr": "45*chairs + 80*tables"
}
},
"solver": "highs",
"options": {
"compute_preset": "cpu-standard",
"time_limit_seconds": 5,
"tags": [
"quickstart"
]
}
}
Klaar voor de details? Volledige API-referentie
Bearer-sleutels
Elke aanvraag draagt uw API-sleutel als bearer-token. Sleutels zijn afgebakend tot de optimalisatie-API, dus een gelekte sleutel kan niets anders in uw account raken.
Authorization: Bearer <your-api-key>
Maak uw eerste sleutel aan in het portaal. Daarna kunt u sleutels oplijsten, aanmaken en intrekken via de API zelf (zie API-sleutels). Bewaar sleutels server-side en roteer ze zonder downtime wanneer nodig.
Hoe een solve verloopt
Oplossen verloopt asynchroon. Een inzending geeft meteen een id terug met status queued; de solve draait op onze infrastructuur; de uitkomst komt naar u toe.
POST /problems -> id, status: queued
POST /problems/{id}/wait -> held open while the solver works
the solver picks it up -> running
it finishes -> finished (the wait returns)
GET /problems/{id}/result -> the solution itself
Drie manieren om te weten dat het klaar is. Een webhook bereikt u op het moment dat het probleem klaar is en kost u geen aanvraag; dat is wat een server hoort te gebruiken. POST /problems/{id}/wait houdt tot dan één aanvraag open, dat is wat een script op een laptop hoort te gebruiken. GET /problems/{id}/events streamt dezelfde events over één verbinding en draagt de voortgang van de solver terwijl hij werkt. Elke payload is beknopt, dus de oplossing haalt u nog steeds op met één call.
De inzendingsenvelope
Eén geversioneerde wrapper rond vier onderdelen. Wij beheren en valideren de wrapper; het problem erin is van u.
- api_version
- De wire-versie. Vandaag altijd
"1". - problem
- Uw model: variabelen, beperkingen, een doelfunctie. Opaak voor ons, gevalideerd door de solver.
- solver
- De solver die moet draaien: een concrete solvernaam, of de naam van een meta-solver, dat is een benoemde set solvers waarvan om het even welke de opdracht kan opnemen. Solverinstellingen reizen mee binnen
problem. - options
time_limit_seconds,compute_preset,webhook,idempotency_keyentags. Allemaal optioneel.- options.compute_preset
- De rekenkracht die de solve reserveert, per code (
cpu-standard,gpu-standard). Laat u het weg, dan draait de solver op zijn eigen standaardprofiel.GET /solverslijst elk profiel op en welke solvers het aanbieden.
{
"api_version": "1",
"problem": {
"variables": [
{
"name": "chairs",
"lower": 0
},
{
"name": "tables",
"lower": 0
}
],
"constraints": [
{
"expr": "4*chairs + 8*tables <= 160",
"name": "machine_hours"
},
{
"expr": "2*chairs + 3*tables <= 75",
"name": "material"
}
],
"objective": {
"sense": "max",
"expr": "45*chairs + 80*tables"
}
},
"solver": "highs",
"options": {
"compute_preset": "cpu-standard",
"time_limit_seconds": 5,
"tags": [
"quickstart"
]
}
}
Machineleesbare JSON-schema's: admission_request_v1.json
Het schema typeert problem als een opaak object, want de manager leest het nooit. De sectie hieronder is de vorm die de solvers wel lezen.
Een model beschrijven
Drie sleutels binnen problem: de variabelen waarover u beslist, de constraints die ze moeten respecteren, en één doelfunctie. Expressies zijn strings, en ze zijn lineair.
- variables
- Een lijst. Elk item heeft een
namenodig van letters, cijfers en underscores, niet beginnend met een cijfer.typeiscontinuous(de standaard),integerofbinary.lowerenupperzijn optionele numerieke grenzen; laat er één weg en de variabele is aan die kant onbegrensd. - constraints
- Een lijst van
expr-strings, elk met een optionelenamedie terugkomt in de diagnose. Eenexpris termen links, één getal rechts, verbonden door<=,>=of==. - objective
senseisminofmax;exprgebruikt dezelfde termen, zonder vergelijking en zonder constante. Een constante verschuift alleen de waarde, dus wordt ze geweigerd in plaats van stil weggelaten.
Termen. Een term is een coëfficiënt, een sterretje en een variabelenaam: 4*chairs. Een coëfficiënt van één mag u schrijven als de naam alleen. Termen worden verbonden met een plus of een min en een spatie aan weerszijden: 4*chairs + 8*tables - 2*offcuts. Wat niet lineair is, wordt niet aanvaard: geen product van twee variabelen, geen functies, geen exponenten.
Een variabele integer of binary verklaren is wat een lineair programma in een gemengd geheeltallig programma verandert. Verder verandert er niets in de envelope, en de prijs blijft het basistarief plus de seconden. De uitgewerkt voorbeeld hieronder staat een volledig model met vijf binaire variabelen.
Status en uitkomst
Twee vaste woordenlijsten. status is de levenscyclus van het probleem; zodra het finished bereikt, duidt outcome het antwoord.
| Status | Betekenis |
|---|---|
| pending_input | Aanvaard, nog in afwachting van het model dat u apart oplaadt. |
| queued | Aanvaard en wacht op een oplossing. |
| running | Een solver heeft het opgenomen en is aan het oplossen. |
| finished | Er is een antwoord (zie outcome). De oplossing is klaar om op te halen. |
| failed | De dienst kon de oplossing niet uitvoeren; error_code zegt waarom. Een model dat de gekozen solver niet aankan komt in plaats daarvan op finished (outcome error). |
| cancelled | Door u geannuleerd, of vervallen terwijl het in de wachtrij stond. |
| Uitkomst | Betekenis |
|---|---|
| solved | Het antwoord waar om gevraagd werd: een optimale oplossing of een oplossing binnen de tolerantie. result bevat het punt, objective_value is aanwezig. |
| no_solution | Bewezen: er bestaat geen antwoord zoals gevraagd (onhaalbaar of onbegrensd). termination_status draagt de exacte diagnose. |
| limit | Een budget stopte de solve eerst (tijd, geheugen, iteraties). Bestaat er een incumbent, dan bevat result die en is objective_value aanwezig. |
| error | De solver liep en brak op dit model: numeriek falen, een ongeldig model, of een constraintklasse die deze solver niet kan uitdrukken. Toch een antwoord over dit model. Wordt niet opnieuw geprobeerd. Kies een andere solver of herformuleer. |
Vertak enkel op deze twee velden. De oplossing bevat ook de letterlijke MOI termination_status van de solver (zoals OPTIMAL of TIME_LIMIT) als diagnosedetail; behandel die als weergavetekst, niet als contract.
Idempotentie
Stel options.idempotency_key in op een unieke string. Als u door een netwerkhapering opnieuw indient, geeft de tweede submit het oorspronkelijke probleem terug in plaats van een duplicaat aan te maken (en een dubbele kredietreservering).
"options": { "idempotency_key": "order-4821-solve", "time_limit_seconds": 5 }
Sleutels zijn gebonden aan uw account. Een sleutel hergebruiken geeft altijd het eerste probleem terug dat ermee werd aangemaakt.
Fouten
Fouten komen terug met de passende HTTP-status en een JSON-body. Match op code, toon message en lees details wanneer een veld in fout is.
{ "error": { "code": "invalid_envelope", "message": "...", "details": {...} } }
| HTTP | Code | Wanneer |
|---|---|---|
| 401 | invalid_key | Ontbrekende, onbekende of ingetrokken bearer-sleutel. Beantwoord in de gewone foutenvelope, met een WWW-Authenticate: Bearer-header. |
| 402 | insufficient_credits | De maximale kost van de inzending is hoger dan het beschikbare saldo. Verlaag time_limit_seconds, of kies een goedkoper rekenprofiel. |
| 403 | customer_suspended | Het account is geschorst. |
| 404 | not_found | Geen dergelijk probleem, dergelijke oplossing of sleutel voor deze klant. |
| 409 | last_key | Geweigerd: u kunt uw enige API-sleutel niet intrekken. |
| 410 | purged | De payload van de oplossing is voorbij haar bewaartermijn. |
| 413 | envelope_too_large | De body van de aanvraag is groter dan 20 MB. |
| 422 | invalid_envelope | De envelope is niet door de validatie geraakt. details lijst de velden op. |
| 422 | unknown_solver | De gevraagde solver staat niet in de catalogus. |
| 422 | compute_preset_not_available | Dat rekenprofiel bestaat niet, of de gevraagde solver draait er niet op. |
| 429 | rate_limited | Te veel aanvragen voor dit account. Retry-After zegt hoe lang u moet wachten. |
Limieten en credits
Twee dingen begrenzen uw gebruik: hoeveel solves tegelijk draaien, en hoe lang elk mag draaien.
- Gelijktijdigheid
- Uw
concurrency_cap(zieGET /account) bepaalt hoeveel problemen tegelijk opgelost worden. Daarboven wachten nieuwe submits in de wachtrij. - Credits
- Een solve kost het basistarief van zijn solver plus zijn tarief op het rekenprofiel waarop hij draait, per seconde. De maximale kost is dat tarief over de volledige
time_limit_seconds, en wordt gereserveerd zodra het probleem toegelaten is;GET /quotegeeft hetzelfde cijfer vóór u indient. De afrekening factureert de werkelijk gebruikte seconden, dus een run die vroeger klaar is kost minder en een geannuleerde run factureert wat ze gedraaid heeft.
Veel solves tegelijk? GET /events draagt het hele account op één verbinding, en ?problems=a,b,c beperkt het tot maximaal 100. Voor het antwoord zelf kost een webhook u geen enkele aanvraag.
Problemen
Dien optimalisatieproblemen in en volg ze tot een resultaat.
POST
/problems
Dien één envelope in. Geeft het id en de effectieve limieten terug.
- envelopebody
- Een geversioneerde submit-envelope (zie De envelope).
curl -X POST "$BASE/problems" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d @envelope.json
{
"problem_id": "prb_9f3c...",
"status": "queued",
"time_limit_seconds": 5,
"pricing": {
"compute_preset": "cpu-standard",
"max_runtime_seconds": 5,
"max_cost": 6,
"rates": {"highs": {"base_fee": 1, "effective_rate": 1.0}}
}
}
GET
/problems/{id}
Status, positie in de wachtrij, live voortgang, en de link naar de oplossing zodra ze klaar is.
curl "$BASE/problems/prb_9f3c..." \
-H "Authorization: Bearer $API_KEY"
{
"problem_id": "prb_9f3c...",
"status": "running",
"progress": {"gap": 0.03, "incumbent": 1690.0},
"outcome": null,
"solution_url": null
}
POST
/problems/{id}/wait
Houd de aanvraag open tot het probleem terminaal is. Geeft daarna zijn toestand terug.
- timeout_secondsquery
- Hoe lang open blijven, 1 tot 60 seconden, standaard 60. Bij afloop krijgt u de toestand zoals ze is, geen fout, dus een solve die langer duurt dan het plafond vraagt een herhaalde call.
curl -X POST "$BASE/problems/prb_9f3c.../wait?timeout_seconds=60" \
-H "Authorization: Bearer $API_KEY"
{
"problem_id": "prb_9f3c...",
"status": "finished",
"outcome": "solved",
"termination_status": "OPTIMAL",
"usage": {"billable_seconds": 0.4, "wall_seconds": 0.4,
"compute_preset": "cpu-standard"},
"cost": {"credits": 2, "settled_at": "2026-09-18T09:12:05Z"}
}
GET
/problems/{id}/events
Server-sent events voor één probleem. Laat het id-segment weg en roep /events aan voor het hele account.
- kindsquery
- Een of meer van status, progress en log, gescheiden door komma's. Alle drie op één probleem; de stroom van de account draagt status en progress, en weigert log.
- problemsquery
- Alleen op /events: tot 100 door komma's gescheiden ids. Het hele account als u het weglaat.
- Last-Event-IDheader
- Het id van het laatste event dat u verwerkt hebt. De stream hervat daarvandaan, zodat een verbroken verbinding niets verliest.
# one problem
curl -N "$BASE/problems/prb_9f3c.../events" \
-H "Authorization: Bearer $API_KEY"
# every problem on the account, on one connection
curl -N "$BASE/events" -H "Authorization: Bearer $API_KEY"
event: problem.updated
id: 1758306411.4.0
data: {"v":1,"type":"problem.updated","problem_id":"prb_9f3c...",
"data":{"status":"running","outcome":null}}
event: problem.updated
id: 1758306413.5.0
data: {"v":1,"type":"problem.updated","problem_id":"prb_9f3c...",
"data":{"status":"finished","outcome":"solved"}}
GET
/problems
Lijst uw problemen op, of lees er meerdere tegelijk met ?ids=a,b,c.
- idsquery
- Door komma's gescheiden ids voor een directe multi-get (maximaal 200).
- statusquery
- Filter op status in de levenscyclus.
- tagquery
- Filter op een tag die u instelt in options.tags.
- limit / offsetquery
- Paginavenster (limiet maximaal 200).
# recent problems, newest first
curl "$BASE/problems?status=running&limit=20" \
-H "Authorization: Bearer $API_KEY"
# bulk status: one call for many ids (<=200)
curl "$BASE/problems?ids=prb_a,prb_b,prb_c" \
-H "Authorization: Bearer $API_KEY"
{
"problems": [
{"problem_id": "prb_a", "status": "finished", ...},
{"problem_id": "prb_b", "status": "running", ...}
]
}
POST
/problems/{id}/cancel
Annuleer een probleem in de wachtrij of in uitvoering. Reeds verbruikte tijd blijft factureerbaar.
curl -X POST "$BASE/problems/prb_9f3c.../cancel" \
-H "Authorization: Bearer $API_KEY"
{"problem_id": "prb_9f3c...", "status": "cancelled"}
Oplossingen
Haal resultaten op, één per één of in bulk.
GET
/problems/{id}/result
De volledige oplossingsenvelope. Ze ophalen voltooit de levering.
# 302 to a presigned URL by default; inline=1 returns the body itself.
curl "$BASE/problems/prb_9f3c.../result?inline=1" \
-H "Authorization: Bearer $API_KEY"
{
"problem_id": "prb_9f3c...",
"outcome": "solved",
"solution": {
"solver_used": "highs",
"outcome": "solved",
"termination_status": "OPTIMAL",
"objective_value": 1750.0,
"result": {"values": {"chairs": 30.0, "tables": 5.0}},
"metering": {"wall_seconds": 0.4}
}
}
Solvers en account
De solvercatalogus en de staat van uw account.
GET
/solvers
De catalogus van solvers met de prijzen van elk, en elk rekenprofiel dat een inzending kan benoemen.
curl "$BASE/solvers" -H "Authorization: Bearer $API_KEY"
{
"solvers": [
{"name": "highs", "display_name": "HiGHS",
"description": "LP, MIP", "availability": "ok",
"pricing": {
"base_fee": 1, "runtime_rate": 1.0,
"default_preset": "cpu-standard",
"presets": [
{"code": "cpu-standard", "name": "CPU Standard",
"multiplier": 1.0, "is_default": true,
"effective_rate": 1.0},
{"code": "cpu-performance", "name": "CPU Performance",
"multiplier": 2.5, "is_default": false,
"effective_rate": 2.5}
]}}
],
"compute_presets": [
{"code": "cpu-standard", "name": "CPU Standard",
"description": "2 vCPU, 4 GiB memory", "is_gpu": false}
]
}
GET
/quote
Wat een inzending zou kosten, volgens dezelfde regel als bij de toelating. Zelfde solver, zelfde profiel, zelfde looptijd, zelfde cijfer.
- solverquery
- Verplicht. De naam van een solver of een meta-solver.
- compute_presetquery
- Optioneel. Standaard het eigen standaardprofiel van die solver.
- time_limit_secondsquery
- Optioneel. Standaard de waarde van het platform, geplafonneerd op het maximum van het platform.
# what a 60 second solve on HiGHS would cost, on the larger CPU preset
curl "$BASE/quote?solver=highs&compute_preset=cpu-performance&time_limit_seconds=60" \
-H "Authorization: Bearer $API_KEY"
{
"solver": "highs",
"compute_preset": "cpu-performance",
"max_runtime_seconds": 60,
"max_cost": 151,
"rates": {"highs": {"base_fee": 1, "effective_rate": 2.5}}
}
max_cost is wat een inzending onder deze voorwaarden reserveert: de duurste kandidaat die de volledige looptijd draait. Een meta-solver antwoordt één tarief per lidsolver die het profiel aanbiedt.
GET
/account
Creditsaldo, gelijktijdigheidslimiet, accountstatus.
curl "$BASE/account" -H "Authorization: Bearer $API_KEY"
{
"name": "Acme Corp",
"status": "active",
"concurrency_cap": 4,
"credit_balance": 4820,
"credit_available": 4770
}
Webhooks
Configureer en inspecteer uw uitgaande callbacks. Zie de webhookgids voor de handtekeningen.
GET
/account/webhook
Leest uw callback-URL, uw gebeurtenisfilters en de eerste tekens van het ondertekeningsgeheim. Het geheim zelf wordt nooit teruggegeven; roteer als u het kwijt bent.
curl "$BASE/account/webhook" -H "Authorization: Bearer $API_KEY"
{
"url": "https://acme.example/hook",
"events": ["terminal"],
"secret_prefix": "whsec_<first 8>"
}
PUT
/account/webhook
Stelt de callback-URL in of wist ze, en kiest welke gebeurtenissen ze ontvangt. Gevalideerd (https, bereikbaar) bij het opslaan.
- urlbody
- Het https-endpoint, of een lege string om het te wissen.
curl -X PUT "$BASE/account/webhook" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://acme.example/hook", "events": ["terminal", "progress"]}'
{
"url": "https://acme.example/hook",
"events": ["terminal", "progress"],
"secret_prefix": "whsec_<first 8>"
}
POST
/account/webhook/rotate
Maakt een nieuw ondertekeningsgeheim aan. Het vorige blijft 24 uur ondertekenen, zodat leveringen die onderweg zijn nog kloppen.
curl -X POST "$BASE/account/webhook/rotate" \
-H "Authorization: Bearer $API_KEY"
{
"url": "https://acme.example/hook",
"events": ["terminal"],
"secret_prefix": "whsec_<first 8>",
"secret": "whsec_<the full secret, shown once>"
}
POST
/account/webhook/test
Stuur nu een ondertekend voorbeeldevent naar uw endpoint.
curl -X POST "$BASE/account/webhook/test" \
-H "Authorization: Bearer $API_KEY"
{
"id": "dlv_4c1e...",
"event_id": "evt_8a02...",
"type": "test",
"status": "delivered",
"attempts": [{"at": "2026-09-18T09:12:04Z", "http_status": 200, "error": null}]
}
GET
/account/webhook/deliveries
Recente leveringspogingen met hun status en laatste fout.
curl "$BASE/account/webhook/deliveries" \
-H "Authorization: Bearer $API_KEY"
{
"deliveries": [
{"id": "dlv_4c1e...", "event_id": "evt_8a02...",
"type": "problem.updated", "problem_id": "prb_a",
"subscriber": "account", "url": "https://acme.example/hook",
"status": "dead", "attempts": [{"at": "2026-09-18T09:12:04Z",
"http_status": 500, "error": "server error"}]}
],
"next_cursor": null
}
POST
/account/webhook/deliveries/{id}/retry
Verstuur een levering uit de dead-letter opnieuw.
curl -X POST "$BASE/account/webhook/deliveries/dlv_4c1e.../retry" \
-H "Authorization: Bearer $API_KEY"
{"id": "dlv_4c1e...", "status": "pending", "attempts": [...]}
API-sleutels
Beheer sleutels programmatisch. De eerste sleutel maakt u aan in het portaal.
GET
/account/keys
Lijst uw sleutels op per prefix. Het geheim wordt nooit opnieuw getoond.
curl "$BASE/account/keys" -H "Authorization: Bearer $API_KEY"
{
"keys": [
{"id": 41, "name": "prod", "prefix": "3f9c1a2b",
"created_at": "2026-07-01T09:00:00Z", "expires_at": null}
]
}
POST
/account/keys
Maak een nieuwe sleutel aan. Het geheim wordt precies één keer teruggegeven.
- namebody
- Een label voor de sleutel (optioneel).
- expires_daysbody
- Aantal dagen tot vervaldatum, of weglaten voor geen.
curl -X POST "$BASE/account/keys" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "prod-2026", "expires_days": 365}'
{"id": 42, "name": "prod-2026", "prefix": "7b2e4f9d",
"key": "<full key, shown once>"}
DELETE
/account/keys/{id}
Trek een sleutel in. Uw laatst overblijvende sleutel is beschermd (409).
curl -X DELETE "$BASE/account/keys/41" \
-H "Authorization: Bearer $API_KEY"
{"revoked": 41}
Webhooks
Laat ons u verwittigen. Stel options.webhook in per probleem, of een standaard endpoint op uw account, en ontvang een ondertekend event zodra een probleem klaar is. Dit is de goedkoopste manier om in productie te draaien: ze kost u geen enkele eigen aanvraag.
Gebeurtenissen. Een abonnement kiest via options.webhook.events wat het ontvangt, per probleem of als standaard op de account. terminal, de standaard, stuurt problem.updated bij een eindstatus en de problem.delivered die volgt op de eerste download. running stuurt elke problem.updated. progress stuurt problem.progress tijdens het oplossen, elke tik een cumulatieve momentopname waarbij de laatste geldt. settled stuurt problem.settled, zodra de kost bekend is. De payloads zijn licht (status, outcome, termination_status en een solution_url), dus haalt u de inhoud op met één oproep. Logregels worden nooit aan een webhook geleverd: ze bestaan enkel op de stroom van één probleem.
Controleer elke levering. Wij ondertekenen volgens het Standard Webhooks-schema, dat een bibliotheek die u al gebruikt wellicht voor u afhandelt. Drie headers dragen het: webhook-id, webhook-timestamp en webhook-signature, die laatste een base64 HMAC-SHA256 over {id}.{timestamp}.{body} met uw ondertekeningsgeheim, geschreven als v1,.... Weiger een tijdstempel ouder dan vijf minuten en aanvaard om het even welke van de door spaties gescheiden waarden: bij een rotatie ondertekenen beide geheimen 24 uur lang, zodat een ontvanger die het nieuwe nog niet heeft opgepikt toch een waarde vindt die klopt.
import base64, hashlib, hmac, json, os, time
# The Standard Webhooks scheme. The secret is shown once by /account/webhook and
# spells its key in base64 after a whsec_ prefix; sign with the decoded bytes.
SECRET = os.environ["SOLVE_WEBHOOK_SECRET"] # "whsec_<base64>"
KEY = base64.b64decode(SECRET.removeprefix("whsec_"))
TOLERANCE = 300 # five minutes, as we send
def handle(request):
body = request.get_data() # the raw bytes, unparsed
msg_id = request.headers["webhook-id"] # "evt_..." and the dedupe key
sent_at = int(request.headers["webhook-timestamp"])
if abs(time.time() - sent_at) > TOLERANCE: # refuse a replayed delivery
return "stale timestamp", 400
signed = f"{msg_id}.{sent_at}.".encode() + body
expect = "v1," + base64.b64encode(
hmac.new(KEY, signed, hashlib.sha256).digest()
).decode()
# A rotation signs with both secrets for 24 hours, so the header may carry
# several space-separated values and any one of them matching is enough.
presented = request.headers["webhook-signature"].split(" ")
if not any(hmac.compare_digest(expect, value) for value in presented):
return "bad signature", 400
event = json.loads(body)
if event["type"] == "problem.updated" and event["data"]["status"] == "finished":
fetch_result(event["problem_id"]) # payloads are thin: pull the body
# every other type ("problem.delivered", "problem.progress", "problem.settled",
# "test") just needs the 200 back
return "", 200
Nieuwe pogingen. Een levering wordt 24 uur lang opnieuw geprobeerd met oplopende wachttijd en daarna dood gemarkeerd. Raadpleeg het logboek op GET /account/webhook/deliveries en speel een dode levering opnieuw af met POST /account/webhook/deliveries/{delivery_id}/retry, vanuit het portaal of uw eigen code; rijen blijven 90 dagen bewaard. Dezelfde gebeurtenis kan meer dan eens aankomen, gebruik webhook-id dus als ontdubbelingssleutel.
API-sleutels beheren
Uw eerste sleutel maakt u aan in het portaal (u hebt een sleutel nodig om de API aan te roepen). Daarna beheert u ze via de API, zodat rotatie geautomatiseerd kan worden.
Roteer zonder downtime. Maak de opvolger aan, rol die uit en trek daarna de oude sleutel in. Uw enige sleutel intrekken wordt geweigerd, zodat u zichzelf niet kunt buitensluiten.
# Rotate with no downtime: mint the successor, switch over, then revoke.
# 1. Create the replacement (the secret is shown exactly once).
curl -s -X POST "$BASE/account/keys" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "prod-2026"}'
# -> {"id":42,"name":"prod-2026","prefix":"7b2e4f9d","key":"<full key, shown once>"}
# 2. Deploy the new key everywhere, then revoke the old one by id.
curl -s -X DELETE "$BASE/account/keys/41" -H "Authorization: Bearer $NEW_KEY"
# -> {"revoked": 41}
Python-SDK
Schrijf het model in Python en krijg de oplossing terug als objecten, met versturen, wait, webhookverificatie en idempotente retries al ingepakt. De Studio draait ze in de browser, zodat u de vorm kunt lezen voor u iets aansluit.
De envelope is het contract, dus blijft de SDK een dunne gemakslaag erbovenop. Alles wat jumpy kan, kan uw eigen client ook.
Uitgewerkte voorbeelden
Een productiemix (LP) is de quickstart hierboven. Hier is dezelfde envelope met geheeltallige beslissingen: vijf kandidaat-projecten, één budget, kies de deelverzameling die het meest waard is.
{
"api_version": "1",
"problem": {
"variables": [
{
"name": "project_1",
"type": "binary"
},
{
"name": "project_2",
"type": "binary"
},
{
"name": "project_3",
"type": "binary"
},
{
"name": "project_4",
"type": "binary"
},
{
"name": "project_5",
"type": "binary"
}
],
"constraints": [
{
"expr": "12*project_1 + 5*project_2 + 8*project_3 + 21*project_4 + 9*project_5 <= 30",
"name": "budget"
}
],
"objective": {
"sense": "max",
"expr": "18*project_1 + 6*project_2 + 12*project_3 + 30*project_4 + 11*project_5"
}
},
"solver": "highs",
"options": {
"compute_preset": "cpu-standard",
"time_limit_seconds": 5,
"tags": [
"budget"
]
}
}
Post het precies zoals in de quickstart. Het antwoord kiest project_3 en project_4, besteedt 29 van de 30, en meldt objective_value 42 met termination_status OPTIMAL.
Een leveringsroute (VRP) en een shiftbezetting zijn groter dan een codepaneel, dus staan ze in de Studio, geschreven in Python en ingediend via dezelfde envelope.
Probeer ze in de Studio
Elk voorbeeld laadt met één klik in de live editor. Geen sleutel nodig om de sandbox uit te voeren.
Uw eerste solve is vijf minuten ver
Draai een echt model op een echte solver, in de browser. Geen account, geen kredietkaart.