A REST API for your company's manufacturing data: quotes, jobs, clients, suppliers, purchase orders, RFQs, inventory, and more. Connect your ERP, automate order intake, sync dashboards, or react to changes with webhooks.
Full API reference →Create an API key in Settings → Integrations, pick the scopes it needs, and copy it (it's shown only once). Then make your first call:
curl https://app.forgemrp.com/api/v1/clients \
-H "Authorization: Bearer fmrp_live_..."A successful response wraps results in data:
{
"data": [
{ "id": "…", "name": "Acme Co", "created_at": "…", "updated_at": "…" }
],
"next_cursor": null
}Send your key as a bearer token on every request. Keys are company-wide and carry a fixed set of scopes. Treat a key like a password; if one leaks, revoke it in Settings → Integrations.
Authorization: Bearer fmrp_live_<keyid>_<secret>_<crc>Each endpoint requires a scope such as quotes.view or clients.create. A write scope implies the matching read scope (holding quotes.create also lets you read quotes). A key can only be granted scopes its creator has. Missing the required scope returns 403 insufficient_scope.
List endpoints are keyset-paginated. Pass ?limit= and follow next_cursor until it is null. Every resource includes updated_at, so you can keep a local copy in sync by paging and recording the newest updated_at you have seen.
cursor = None
while True:
url = "https://app.forgemrp.com/api/v1/quotes?limit=100"
if cursor: url += f"&cursor={cursor}"
res = requests.get(url, headers=headers).json()
handle(res["data"])
cursor = res["next_cursor"]
if not cursor: breakErrors use a consistent envelope:
{ "error": { "code": "not_found", "message": "Quote not found" } }400 bad_request, 422 validation_error: check the message401 unauthorized: bad or missing key403 insufficient_scope: the key lacks the scope404 not_found, 409 conflict (e.g. a locked quote)429 rate_limited: back off and retry after Retry-AfterEvery response carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset (seconds until the window resets). A 429 also includes Retry-After; wait that many seconds before retrying.
Send an Idempotency-Key header on POST requests. If a request is retried with the same key, the original response is returned and no duplicate is created, which keeps flaky networks and retrying automation safe.
curl -X POST https://app.forgemrp.com/api/v1/quotes \
-H "Authorization: Bearer fmrp_live_..." \
-H "Idempotency-Key: 7c9e6679-..." \
-H "Content-Type: application/json" \
-d '{ "clientId": "…" }'Register HTTPS endpoints in Settings → Integrations and subscribe to events like quote.created, job.updated, or * for all. Each delivery is a POST with X-Forge-Event and X-Forge-Signature, an HMAC-SHA256 (hex) of the raw body keyed by your endpoint's whsec_ secret. Verify it before trusting the payload:
import hmac, hashlib
def verify(secret: str, raw_body: bytes, signature: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Deliveries that fail are retried with exponential backoff. The payload is { "type", "created_at", "data" }.