Build with Intellign.
Intellign solves resource-allocation problems — who goes where, what runs when. This page covers the web app, the public API, the Python SDK, and the MCP server, all backed by the same account and engine.
Overview — what Intellign does
Intellign solves resource-allocation problems: who goes where, what runs when, how to use limited people, vehicles, budget, or time well.
You describe a problem — “assign 40 nurses to 6 clinics so skills match and nobody is overloaded” — attach your data (a spreadsheet is enough, and if you have no data Intellign can generate a realistic sample), and get back an optimized, explainable plan. Real optimization engines do the math (genetic algorithms, CP-SAT, OR-Tools); AI handles the understanding, data-gap filling, and plain-language explanation.
The four ways to use Intellign
| Surface | Best for | Where |
|---|---|---|
| Web app | Anyone — chat your way from problem to plan | intellign.ai |
| Public API | Developers integrating optimization into their systems | api.intellign.ai/api/public/v1 |
| Python SDK | Python developers — typed client, no HTTP plumbing | pip install intellign |
| MCP server | AI agents (Claude etc.) calling Intellign as tools | mcp.intellign.ai/mcp |
All four run on the same engine and the same account: an API key made in the dashboard works for the API, the SDK, and the MCP server.
Five concepts (the whole mental model)
- Entities — the resources being assigned (nurses, vans, field officers). A table with an
idcolumn. - Targets — what receives them (clinics, routes, districts). Also a table.
- Objectives — what “good” means, with weights. E.g.
skill_matchat weight 70,workload_balanceat 30. - Constraints — hard rules the plan should respect, written in a small formal grammar:
entity.skills superset_of target.required_skills. - Jobs — solving runs asynchronously; you submit a problem, get a
job_id, and poll / stream / get a webhook when the plan is ready.
Getting started
- Create an account at intellign.ai.
- Create an API key: Dashboard → API Keys.
ik_live_...— production. Solves count against your plan.ik_test_...— sandbox: free, doesn't touch quota, capped at 200 entities / 200 targets and shorter solver runs. Start here.- The key is shown once — store it like a password.
- Pick your surface and run the 2-minute example in its section below.
Plans & limits (per key, per minute): free 30 requests, pro 120, enterprise 600. Monthly solve quotas follow your org's plan.
Public API
Base URL: https://api.intellign.ai/api/public/v1
Auth on every request: Authorization: Bearer ik_...
Interactive OpenAPI docs: api.intellign.ai/docs
Two-minute example (curl)
KEY=ik_test_...
# 1. Create a problem (inline data)
curl -s -X POST https://api.intellign.ai/api/public/v1/problems \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{
"spec": {
"problem_type": "assignment",
"entities": {"rows": [
{"id": "n1", "skills": "triage"}, {"id": "n2", "skills": "surgery"}]},
"targets": {"rows": [
{"id": "c1", "required_skills": "triage", "capacity": 1},
{"id": "c2", "required_skills": "surgery", "capacity": 1}]},
"objectives": [{"metric": "skill_match", "weight": 70,
"entity_column": "skills", "target_column": "required_skills"}],
"constraints": [{"expression": "entity.skills superset_of target.required_skills"}]
}}'
# → {"problem_id": "...", "status": "draft", "goals_compiled": 2}
# 2. Solve it
curl -s -X POST https://api.intellign.ai/api/public/v1/problems/<problem_id>/solve \
-H "Authorization: Bearer $KEY"
# → {"job_id": "..."}
# 3. Poll, then fetch the plan
curl -s https://api.intellign.ai/api/public/v1/jobs/<job_id> -H "Authorization: Bearer $KEY"
curl -s https://api.intellign.ai/api/public/v1/jobs/<job_id>/result -H "Authorization: Bearer $KEY"The ProblemSpec format
Everything the headless API solves is a ProblemSpec (version "1"):
{
"spec_version": "1",
"problem_type": "assignment", // assignment | scheduling | routing | allocation | matching
"name": "optional label",
"description": "optional — used when explaining results",
"entities": {"rows": [...]}, // OR {"dataset_id": "ds_..."}; id column default "id"
"targets": {"dataset_id": "ds_..."},
"objectives": [ // at least one
{"metric": "skill_match", "weight": 70, "direction": "maximize",
"entity_column": "skills", "target_column": "required_skills"}
],
"constraints": [ // optional
{"expression": "entity.skills superset_of target.required_skills", "hard": true}
],
"solver_params": {"time_budget_seconds": 60, "generations": null,
"population_size": null, "solver_preference": null},
"quality_mode": "balanced" // fast | balanced | best
}Rules: inline rows max 2,000 (use datasets beyond that); weights (0, 100]; exactly one of rows/dataset_id per side.
Objectives (metric catalog)
| Metric | Meaning | Needs |
|---|---|---|
skill_match | entity's skill set covers the target's requirements | entity_column, target_column |
distance | prefer geographically closer pairings (lat/lon columns) | — |
workload_balance | penalize uneven load vs target capacity | target_column |
attribute_match | exact categorical match | entity_column, target_column |
cost | minimize a numeric entity column | entity_column |
preference_match | scheduling preference alignment | — |
Always current version: GET /capabilities (also lists solvers, grammar, limits).
Constraint grammar
Six forms — anything else is rejected at create time with a precise reason:
| Form | Example |
|---|---|
entity.{a} superset_of target.{b} | entity.skills superset_of target.required_skills |
entity.{a} <op> target.{b} | entity.hours <= target.max_hours (<= >= == < >) |
entity.{a} <op> <number> | entity.age >= 21 |
sum(entity.{a}) <op> target.{b} | sum(entity.load) <= target.capacity |
haversine(entity.{a}, target.{b}) <= km | haversine(entity.location, target.location) <= 25 |
entity.{a} in [values] | entity.region in ['north', 'east'] |
"hard": true (default) = violation weight 100; soft = 60.
Endpoints
| Method + path | Purpose |
|---|---|
GET /capabilities | Metric catalog, grammar, solvers, limits — machine-readable |
GET /templates · GET /templates/{name} | Starter ProblemSpecs to copy and edit |
POST /datasets (multipart file) | Upload CSV/XLSX (≤10 MB, ≤50k rows) → dataset_id; 7-day retention |
GET /datasets/{id} | Metadata + preview |
POST /problems | Register a spec → problem_id (validates + compiles immediately) |
GET /problems/{id} | Spec + status + last job |
POST /problems/{id}/solve | Start solving → job_id (202) |
GET /jobs/{id} | Status + progress (best_fitness, generation) |
GET /jobs/{id}/stream | Server-Sent Events live progress |
GET /jobs/{id}/result | The plan: assignments, metrics; 409 while running |
POST /solve | Natural-language one-shot: {problem, entities_dataset_id, targets_dataset_id} |
POST /webhooks · GET /webhooks · DELETE /webhooks/{id} | Completion notifications (max 5 endpoints) |
GET /webhooks/{id}/deliveries | Delivery log (last 50 attempts) |
POST /sessions · /sessions/{id}/messages · GET /sessions/{id} | Conversational flow over the API |
POST /sessions/{id}/optimize · GET /sessions/{id}/export | Run a ready session; export it as a ProblemSpec |
Errors
Every error has one shape:
{"error": {"code": "problem_not_found", "message": "…", "request_id": "abc123"}}Quote request_id when contacting support.
| Code | HTTP | Meaning |
|---|---|---|
invalid_api_key | 401 | missing / unknown / expired key |
insufficient_scope | 403 | key lacks solve or read scope |
invalid_spec | 400/422 | bad spec, constraint, or upload — message lists every issue |
*_not_found | 404 | dataset / problem / job / template / webhook / session |
dataset_too_large | 413 | >10 MB or >50k rows |
job_not_finished | 409 | result requested before completion |
quota_exceeded | 402 | monthly plan limit reached |
rate_limited | 429 | slow down — honor the Retry-After header |
sandbox_limit_exceeded | 403 | ik_test_ size caps |
idempotency_conflict | 409 | same Idempotency-Key currently in flight |
Idempotency & retries
Send Idempotency-Key: <any-unique-string> on POSTs; replays within 24 h return the original response instead of duplicating work. Safe to retry 429/5xx with backoff.
Webhooks
POST /webhooks {"url": "...", "events": ["job.completed", "job.failed"]}
# → response includes a "secret" — shown once- Headers:
X-Intellign-Event,X-Intellign-Signature: sha256=<HMAC-SHA256(secret, raw_body)> - Body:
{"event": "job.completed", "data": {"job_id", "problem_id", "status", "best_fitness"}} - Verify with a constant-time compare on the raw body. Non-2xx → up to 3 attempts (1s/2s backoff), all logged in
/webhooks/{id}/deliveries.
Python SDK
pip install intellign # core pip install "intellign[pandas]" # + result.to_dataframe() pip install "intellign[ical]" # + result.export_ical() pip install "intellign[mcp]" # + the MCP server
Python ≥ 3.10. Source + runnable examples: github.com/DataBacked-Africa/intellign-python
Two-minute example
from intellign import Client, Problem
client = Client(api_key="ik_test_...") # base_url defaults to api.intellign.ai
problem = (
Problem.assignment(name="Nurse assignment")
.entities(rows=[{"id": "n1", "skills": "triage"},
{"id": "n2", "skills": "surgery"}])
.targets(rows=[{"id": "c1", "required_skills": "triage", "capacity": 1},
{"id": "c2", "required_skills": "surgery", "capacity": 1}])
.maximize("skill_match", weight=70,
entity_column="skills", target_column="required_skills")
.require("entity.skills superset_of target.required_skills")
.quality("fast")
)
result = client.submit(problem).wait()
for a in result.assignments:
print(a["resource_id"], "->", a["target_id"])Client surface (sync Client / async AsyncClient — same methods)
| Area | Methods |
|---|---|
| Discovery | capabilities(), templates(), template(name) |
| Data | upload_dataset(path), get_dataset(id) |
| Problems | create_problem(spec_or_builder), get_problem(id), solve_problem(id), submit(spec_or_builder) |
| Natural language | solve_nl(text, entities_dataset_id, targets_dataset_id) |
| Jobs | Job.status(), .wait(timeout=300), .result(), .stream_progress() |
| Results | .assignments, .metrics, .best_fitness, .to_dataframe(), .export_ical(path) |
| Webhooks | create_webhook(url), list_webhooks(), delete_webhook(id), webhook_deliveries(id) |
| Sessions | create_session(), send_message(id, text), session_state(id), optimize_session(id), export_session(id) |
Builder verbs: .entities()/.targets() (rows or dataset_id=), .maximize()/.minimize(metric, weight, ...), .require(expr, hard=True), .quality("fast|balanced|best"), .time_budget(s), .generations(n), .population(n), .solver(name), .describe(text) → .build().
Async + live progress
from intellign import AsyncClient
async with AsyncClient(api_key="ik_...") as client:
job = await client.submit(problem)
async for event in job.stream_progress():
print(event.get("current_generation"), event.get("best_fitness"))
result = await job.result()Error handling
Every API failure raises a typed subclass of IntelligError with .code, .status_code, .request_id: AuthenticationError, ScopeError, InvalidSpecError, NotFoundError, ConflictError, QuotaError, RateLimitError (.retry_after), SandboxLimitError, SolveFailedError, ServerError.
The client auto-retries 429/502/503/504 (honoring Retry-After), and create_problem/solve_problem accept idempotency_key=.
MCP server (Intellign for AI agents)
The MCP server exposes Intellign as tools any MCP-compatible AI can call. Hosted at https://mcp.intellign.ai/mcp (Streamable HTTP, multi-tenant: your request's Authorization header carries your key, so your quota, limits, and data scoping apply). A human-readable overview lives at mcp.intellign.ai.
Connect
Claude Code:
claude mcp add --transport http intellign https://mcp.intellign.ai/mcp \ --header "Authorization: Bearer ik_..."
Claude Desktop (no remote header support yet — bridge via mcp-remote):
{"mcpServers": {"intellign": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.intellign.ai/mcp",
"--header", "Authorization: Bearer ${INTELLIGN_API_KEY}"],
"env": {"INTELLIGN_API_KEY": "ik_..."}}}}Local/offline alternative:
pip install "intellign[mcp]" INTELLIGN_API_KEY=ik_... intellign-mcp
Tools
| Tool | What it does |
|---|---|
list_templates | list starter problems |
get_template(name) | fetch a full editable ProblemSpec |
create_problem(spec) | register a spec → problem_id (returns readable errors the model can fix) |
solve_problem(problem_id, wait=True, timeout_s=60) | solve; returns assignments, or job_id if async |
get_status(job_id) / get_result(job_id) | poll long solves |
Resources: intellign://schema/spec (ProblemSpec JSON schema), intellign://capabilities (live catalog).
Prompts that work well
- “Get the nurse clinic assignment template from Intellign, replace the nurses with [my data], solve it, and show me who goes where.”
- “Create an Intellign problem assigning these 12 drivers to 5 zones, maximizing zone preference match — read
intellign://schema/specfirst.”
Troubleshooting & FAQ
- 401 invalid_api_key
- Key missing/typo'd, or revoked. Keys are shown once at creation; make a new one if lost.
- 403 sandbox_limit_exceeded
ik_test_keys cap at 200 entities/targets. Use a live key for bigger problems.- 422 invalid_spec
- The message lists every problem (
objectives[0]: entity_column is required…). Fix all listed items; the grammar section above shows valid constraint forms. - 409 job_not_finished
- The solve is still running; poll
GET /jobs/{id}until status iscompleted/failed(compare case-insensitively), or use webhooks /job.wait(). - 429 rate_limited
- Respect
Retry-After. SDK does this automatically. - Webhook signature won't verify
- You must HMAC the raw request body bytes, not re-serialized JSON.
- Can Intellign generate data for me?
- Yes, in the web app / sessions flow: describe the problem and ask for a sample dataset. The headless API expects you to bring data (inline rows or uploaded datasets).
- How fresh are key/plan changes?
- Auth context is cached up to 10 minutes; a revoked key or plan change can take that long to propagate.
Changelog
| Date | Version | Area | Change |
|---|---|---|---|
| 2026-07-14 | 1.0.0 | all | Initial version: API v1, SDK 0.1.x, MCP (hosted, multi-tenant) |