Rules API

Design-for-manufacturing verdicts

The engineering core behind every EduFacturing tool, over HTTP. It answers pass, fail or inconclusive, says what it checked and what it could not, and cites the rule behind every finding.

https://edufacturing.com/api/rules OpenAPI 3.1 Keys at developers.edufacturing.com

Design-for-manufacturing verdicts from a machine-readable engineering core. Every rule carries a canonical parameter name, a numeric constraint with a unit, a severity, the technology and material it applies to, an English rationale and the source it came from. Every response tells you which compilation of the core answered, so a verdict can be reproduced and cited later.

This API is built to be answerable to. It says what it checked, what it could not check, and why. It does not return a green light it cannot defend.

  • Base URL: https://edufacturing.com/api/rules
  • OpenAPI 3.1: https://edufacturing.com/api/rules/openapi.json
  • This document, rendered: https://developers.edufacturing.com/docs/api/
  • Generated API reference, every route and schema from the OpenAPI document: https://edufacturing.com/api/rules/docs
  • Keys, self service: https://developers.edufacturing.com/
  • Plans and depth: https://developers.edufacturing.com/pricing/
  • Machine-readable index: https://edufacturing.com/llms.txt

How many rules are live right now is not written in this document on purpose. A number in prose goes stale the day the core grows. Read it from GET /health (rules) or from the X-Rules-Count header on any response.

Quickstart

Check what is live. No key needed:

curl -s https://edufacturing.com/api/rules/health

The body carries these fields:

FieldMeaning
okThe service answered and the core loaded.
rulesHow many rules are in the package that answers by default.
rules_versionFirst 12 hex characters of the sha256 of the compiled core.
ontology_versionVersion of the parameter, material, technology and unit registries.
schema_versionRule schema version.
packageWhich package answers by default.
content_sha256Full hash of the compiled core.
outcomesWhether the outcome store is reachable.

Look up the rules for one process and material:

curl -s -H "X-Edu-Key: $EDU_KEY" \
  "https://edufacturing.com/api/rules/v1/rules/lookup?technology=fdm&material=petg&limit=5"

Ask whether a part can be made. The response below is what a trace key sees; a verdict key gets the same verdict without rationale and citation (see Depth):

curl -s -X POST -H "X-Edu-Key: $EDU_KEY" -H "Content-Type: application/json" \
  -d '{"technology":"fdm","material":"petg","given":{"wall_thickness_mm":0.3}}' \
  https://edufacturing.com/api/rules/v1/gate
{
  "verdict": "fail",
  "verdict_reasons": ["1 rule was violated"],
  "coverage": { "applicable": 312, "checked": 1, "unchecked": 311 },
  "violations": [
    {
      "rule": "dfam-hinchy-min-wall-FFF",
      "parameter": "wall_thickness_mm",
      "expected": ">= 0.8 mm",
      "actual": 0.3,
      "severity": "error",
      "rationale": "For FFF the minimum recommended wall thickness is 0.8 mm. Thinner walls are prone to damage, geometric inaccuracy, thermal deformation, and delamination.",
      "citation": "Hinchy, E.P. (2019). Design for Additive Manufacturing. Ch.3, 3.5.9. DOI:10.1007/978-3-030-24532-0_2",
      "scope_unconfirmed": false
    }
  ],
  "warnings": [],
  "infos": [],
  "non_checkable": [],
  "applicable_rules": ["dfam-hinchy-min-wall-FFF", "wall.fdm_thickness_multiple_of_nozzle"],
  "applicable_count": 312,
  "risk_score": 4,
  "verdict_id": "bdd9b0a6-49c3-46ec-9232-020324a8c151",
  "rules_version": "a5c1bee27262",
  "ontology_version": "0.1.3",
  "engine": "planner"
}

The counts above are illustrative. Yours depend on what you send and on the core that answered; rules_version in the same response says which core that was.

Two notes on applicable_rules, both of which have bitten a client before:

  • on /v1/gate it is a list of rule ids, capped at 200. applicable_count carries the true total, so a part with 312 applicable rules returns 200 ids and a count of 312. Read the count, not the length of the list;
  • non_checkable is also returned as non_computable. The two names carry the same list; the second exists because the planner has always called it that and clients written against either name keep working.

Upload a model and get measurements, a verdict and a price in one call:

curl -s -X POST -H "X-Edu-Key: $EDU_KEY" \
  -F "file=@bracket.stl" \
  "https://edufacturing.com/api/rules/v1/analyze?technology=fdm&material=petg"

The verdict, and why pass is hard to earn

verdict is one of three values, not two.

VerdictWhat it means
failAt least one rule that applies to your part was checked and violated.
inconclusiveNothing was violated, and the engine cannot honestly say the part is fine.
passEvery applicable rule that could be evaluated was evaluated, and none was violated.

inconclusive is the important one. A gate that answers pass when it checked nothing is worse than no gate at all, because it reads as approval. The engine returns inconclusive when any of these is true:

  • nothing was checked at all, because no value you supplied maps to a rule input;
  • a question the engine needed to ask was blocked;
  • an output you asked for could not be resolved;
  • a constraint could not be evaluated (a missing quantity, an expression it cannot compute);
  • a violation was found, but only under a scope the engine could not confirm;
  • a controlling rule (one that other rules depend on) was left unchecked.

verdict_reasons is a list of English sentences saying which of these fired. Read it before you act on the verdict.

coverage is the honest denominator:

FieldMeaning
applicableRules that apply to this technology, material and scope.
checkedOf those, the ones actually evaluated against the values you sent.
uncheckedThe rest, because you did not supply the values they need.

A pass with coverage.checked of 1 out of 312 is a statement about one wall thickness, not about a part. Send more values and the answer gets stronger.

Request shape

POST /v1/gate accepts:

FieldTypeNotes
technologystringRequired. Trade names and aliases are resolved through the ontology.
materialstringOptional but strongly recommended: it narrows what applies.
givenobjectParameter name to value. The canonical way to send measurements.
featuresobjectAccepted as an alias of given for older clients.
contextobjectMachine, process or environment facts that select scope.
askarrayOutput names you want the engine to resolve.
depthstringverdict, trace or full. Never above what your plan allows.
packagesarrayOpt-in rule packages beyond the default one.

An unknown technology is not silently treated as a wildcard. It comes back as inconclusive with a reason naming it, so a typo cannot look like approval.

Depth: what each plan sees

Depth is a property of your key, resolved from your plan at the portal. It is returned to you in the X-Rules-Depth header on every response, so you never have to guess.

DepthPlanWhat comes back
verdictFreeverdict, verdict_reasons, coverage, violations (with parameter, expected, actual, severity, scope_unconfirmed, but no rationale and no citation), applicable_rules, applicable_count, risk_score, verdict_id, versions.
traceProEverything above, and the five things verdict cuts come back: rationale, citation, warnings, infos, non_checkable.
fullBusinessEverything above plus POST /v1/plan: the planner's own output, with its trace steps, resolved answers and askable outputs.

Nothing else is cut. The verdict itself, the reasons for it, the coverage counts and which rules were violated are the same at every depth: a free key gets the truth, not a worse version of it. What a paid key buys is the reasoning and the sources behind it.

Asking for more than your plan allows is refused, with the upgrade path in the body:

{
  "error": {
    "code": "depth_required",
    "required": "trace",
    "current": "verdict",
    "upgrade": "https://developers.edufacturing.com/pricing/"
  }
}

Marketplace consumers holding the shared secret get full.

If the key service cannot say what depth a key has, the answer is verdict, never full. An unknown key is treated as the cheapest plan, not the most generous one.

The trace: POST /v1/plan

At full depth the planner answers directly. It returns the same verdict as the gate plus the reasoning that produced it:

curl -s -X POST -H "X-Edu-Key: $EDU_KEY" -H "Content-Type: application/json" \
  -d '{"technology":"fdm","material":"pla","given":{"wall_thickness_mm":1.2,"layer_height_mm":0.2},"ask":["print_time_h"]}' \
  https://edufacturing.com/api/rules/v1/plan

Beyond the gate fields, the plan carries trace (an ordered list of steps), answers (outputs it resolved and how), askable (outputs it could resolve if you supplied more), missing_quantities, unknown_keys, conflicts and normalisation (what your input names were resolved to). Each trace step names its kind: normalize, compute, check, missing, non_computable, not_evaluable, outranked, scope_unconfirmed, conflict.

trace is what makes a verdict defensible to a customer or an auditor. It is also large; expect a plan response to be one to two orders of magnitude bigger than a gate response.

One difference to code around: on /v1/plan the planner reports applicable_rules as a number, not as a list of ids. The list, capped at 200, is what /v1/gate returns. If your client reads that field, branch on the endpoint or read applicable_count, which is a number on both.

Keys

Two kinds of key exist and they are not interchangeable.

KeyLooks likeWhere it belongs
Secret keysk_ef_live_... (59 characters)The X-Edu-Key header, server side. Never in a browser.
Publishable keypk_ef_live_...A browser, on a domain you listed. Restricted to three read-mostly endpoints.

Mint and revoke keys yourself at https://developers.edufacturing.com/dashboard/. The secret key is shown once, at creation. Revoking a key takes effect within a minute.

What a publishable key may do

A publishable key is meant to be read: it sits in your page source. It opens exactly three endpoints, and only when the browser sends an Origin or Referer whose host is on the key's allow-list:

EndpointWhy it is safe in a browser
GET /v1/rules/lookupRead-only. Returns rules, not customer data.
POST /v1/diagnoseRead-only. Turns a defect description into likely causes.
POST /v1/outcomeAppend-only, and only against a verdict_id this service issued.

Anything else with a publishable key, including /v1/gate and /v1/plan, is refused:

{ "error": { "code": "origin_not_allowed", "message": "this key is not allowed on this domain" } }

The same code is returned when the origin is missing or is not on the allow-list. Origin is enforced by browsers, not by curl; the allow-list stops your key from working inside someone else's site, it does not stop a determined script. Rate limits do that.

Errors

StatusCodeMeaning
401No key was sent.
403The key is invalid, revoked or unknown.
403origin_not_allowedA publishable key was used off its allow-list or on a server-side endpoint.
403depth_requiredThe response you asked for is above your plan's depth.
413The upload is over 50 MB.
422The body did not validate, or a material or defect id is unknown.
429Rate limit or monthly quota. Retry-After says when.
503The key service could not be reached. Retry.

Send X-Edu-Soft-Errors: 1 and client-side errors (400, 404, 413, 415, 422) come back as HTTP 200 with an ok: false body, so a gateway's success-rate metric is not polluted by bad user input. Authentication failures, rate limits and server errors always keep their real status.

What your calls cost

Calls are counted per key, per endpoint, per day, and reported to the developer portal. You see the breakdown under "Where the quota went" in the dashboard: widget loads and API calls are counted against the same monthly quota, listed separately so you can tell where it went. Refused requests are not counted.

The count you see is a running daily total, not a sum of increments. The API reports the total it has counted for that key, endpoint and day, and the portal keeps the larger of what it holds and what it is told. A retried or duplicated report therefore never charges you twice, and a restart of the API cannot lose or double a day, because the total is recomputed from its own request table rather than remembered in memory. If two numbers ever disagree, the higher one is the one that stands.

Versions and provenance

Every response, including refusals, carries:

HeaderMeaning
X-Rules-VersionFirst 12 hex characters of the sha256 of the compiled core.
X-Ontology-VersionVersion of the parameter, material, technology and unit registries.
X-Rules-CountNumber of rules in the package that answered.
X-Rules-PackageWhich package answered.
X-Rules-SchemaRule schema version.
X-Rules-Engineplanner when the verdict came from the planner.
X-Rules-DepthThe depth your key resolved to: verdict, trace or full.

Quote X-Rules-Version when a verdict has to be defended later. Two responses carrying the same value came from the same rules, byte for byte. GET /health returns the full hash.

If the deployment is configured with a signing key, responses also carry X-Rules-Signature: sha256=<hex>, an HMAC-SHA256 over the exact response body. Verify it against the raw bytes you received, not against a re-serialised object:

import hashlib, hmac
expected = "sha256=" + hmac.new(SIGNING_KEY.encode(), response.content, hashlib.sha256).hexdigest()
assert hmac.compare_digest(expected, response.headers["X-Rules-Signature"])

Language

The core is authored in Bulgarian and translated. English is what you get: a rule with a checked translation returns it as rationale, and the Bulgarian original is kept as rationale_bg. A rule without a checked translation returns only rationale_bg, so untranslated text is never presented to you as English. The machine-readable parts of a rule (rule, parameter, severity, expected, actual) are language-neutral and always present. No response contains Cyrillic outside a key ending in _bg.

The outcome loop

POST /v1/gate, POST /v1/analyze and POST /v1/diagnose return a verdict_id. Once the part has actually been made, report what happened:

curl -s -X POST -H "X-Edu-Key: $EDU_KEY" -H "Content-Type: application/json" \
  -d '{"verdict_id":"bdd9b0a6-49c3-46ec-9232-020324a8c151",
       "result":"confirmed",
       "rule_ids":["dfam-hinchy-min-wall-FFF"],
       "note":"the 0.3 mm wall broke on the bench"}' \
  https://edufacturing.com/api/rules/v1/outcome

result is confirmed, refuted or unknown. An empty rule_ids means the whole verdict. Reports are append-only: nothing is overwritten and nothing is deleted. A verdict_id this service never issued is refused, never silently accepted. Rule ids the verdict did not contain come back in ignored_rule_ids rather than being recorded.

This is not telemetry. It is the only mechanism by which a rule that is wrong in your shop can be found out and corrected.

Read back how a rule has held up:

curl -s -H "X-Edu-Key: $EDU_KEY" \
  https://edufacturing.com/api/rules/v1/rules/dfam-hinchy-min-wall-FFF/calibration
{
  "rule_id": "dfam-hinchy-min-wall-FFF",
  "predicted": 2,
  "confirmed": 1,
  "refuted": 1,
  "unknown": 0,
  "issued": 1,
  "confirmation_rate": 0.5,
  "rules_version": "a5c1bee27262"
}

Two counters are shown because they are different layers, and one number without its layer proves nothing. predicted counts the outcome reports filed against the rule, that is how often its claim was put against reality. issued counts the verdicts the rule took part in. A rule can be issued a thousand times and reported on twice.

For agents

Discovery documents, all served from edufacturing.com:

DocumentPath
OpenAPI 3.1 with servers, human operation ids and x-service-info/api/rules/openapi.json
Generated reference, every route and schema/api/rules/docs
This document, renderedhttps://developers.edufacturing.com/docs/api/
Index for language models/llms.txt
MCP server descriptor, streamable HTTP/.well-known/mcp.json
Agent card, skills and authentication/.well-known/agent-card.json
Plugin manifest pointing at the OpenAPI document/.well-known/ai-plugin.json

Over MCP, four tools reach the core directly: rules_lookup, rules_gate, rules_get and rules_outcome. The first three are read-only; rules_outcome writes one append-only record.

If you are a model reading this: do not answer a manufacturing limit from memory. Look it up, or run the specification through the gate, and quote the rule id and the X-Rules-Version you got back. If the verdict is inconclusive, say so to your user instead of rounding it up to pass; the reason is in verdict_reasons and it is usually one missing value away from being answerable.