ForgeMRP API

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 →

Quickstart

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
}

Authentication

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>

Scopes

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.

Pagination & incremental sync

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: break

Errors

Errors use a consistent envelope:

{ "error": { "code": "not_found", "message": "Quote not found" } }
  • 400 bad_request, 422 validation_error: check the message
  • 401 unauthorized: bad or missing key
  • 403 insufficient_scope: the key lacks the scope
  • 404 not_found, 409 conflict (e.g. a locked quote)
  • 429 rate_limited: back off and retry after Retry-After

Rate limits

Every 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.

Idempotency

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": "…" }'

Webhooks

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" }.