Using the API
MCA Manager exposes a REST API (v1) for automating intake, syncing deals and
payments, and integrating your stack. Everything below is also in the
machine-readable spec at /api/openapi.json and the
interactive API reference, where you can send real
requests and download a Postman collection.
Authentication and scopes
Requests authenticate with a per-tenant API key sent as a bearer token on your company host:
Authorization: Bearer mca_live_…
Keys are created under Settings → API keys (guide) with
per-resource scopes (deals:read, payments:write, *:read…; write
implies read), an optional expiry and an IP allow-list. A test key
(mca_test_…) is served by your sandbox tenant, or read-only until one exists.
Money moves need an action scope by name — deals:fund (move a deal to
FUNDED), payments:settle (mark a payment PAID or BOUNCED) and
payments:refund (refunds and overpayment decisions). No :write grant or
wildcard carries them. A key or app can never be given more than the person who
creates it could do on screen.
An integration can also be a connected app using OAuth 2.0 — client
credentials for a service, or the authorization-code flow for an app acting on
behalf of a person. Its access token (mca_at_…) is sent the same way. See
Connected apps.
Start with GET /api/v1/whoami: it returns the key's (or app's) scopes, the
tenant it is pointed at and the rate limit.
Responses and errors
Every response carries X-Request-Id. Errors share one envelope:
{ "error": "request did not validate", "code": "validation_error",
"details": [{ "path": "amount", "message": "money must be a decimal string…" }],
"requestId": "6f0c…" }
| Status | Codes |
|---|---|
| 400 | validation_error, invalid_id, invalid_json, invalid_sort, invalid_expand, invalid_cursor, bad_request |
| 401 | unauthorized (missing, invalid or expired key or token) |
| 403 | forbidden (scope missing, key read-only, address not allowed), missing_scope (an action scope or the scope behind an expand=) |
| 404 | not_found |
| 409 | conflict (illegal state transition, funding block, duplicate) |
| 422 | invalid_reference, idempotency_key_reused |
| 429 | rate_limited — Retry-After gives the seconds until the window reopens |
| 500 | internal_error — quote the requestId |
| 503 | unavailable — a write while the rate limiter cannot count; retry after Retry-After |
Lists: pagination, sorting, filtering
GET /api/v1/deals?state=FUNDED&limit=100&sort=fundedAt:desc&expand=merchant&fields=id,dealNumber,fundedAmount
→ { "deals": [...], "count": 100, "hasMore": true, "nextCursor": "MTA0Mg" }
limit(1–200, default 50) andcursor(passnextCursorback) page through results; cursors are stable under inserts.sort=field:asc|descfrom each resource's documented sortable fields;idis always available and used as the tiebreak.expand=includes relations (e.g.merchant,paymentson deals) — each needs that resource's read scope, sodeals:readalone cannot pull payments;fields=returns only the named columns (plusid).ids=1,2,3,createdSince,createdBefore,updatedSincework everywhere;q=(free text) only on the lists the reference marks searchable — elsewhere it is refused with 400 rather than ignored. Each resource adds its own filters (dealstate, paymentstatus/type/scheduledSince, leadstatus/source…).
Writes
- Send
Idempotency-Keyon POSTs. The first request runs; a repeat with the same key and body returns the stored response (Idempotency-Replayed: true) for 24 hours; the same key with a different body is refused with 422. - Money is a decimal string with at most two places (
"425.00"); a cent-exact number is accepted, anything else is rejected. Amounts come back as decimal strings. - Timestamps are UTC ISO-8601; dates as
YYYY-MM-DD. - Deal and payment writes run the same code as the staff screens: a deal created through the API gets its deal number, house submission and stip matrix;
PATCH /deals/{id}withstateruns the state machine and funding blocks; marking a paymentPAIDposts the ledger entry. Every write is audited and emits the matching webhook event.
Rate limits
120 requests per minute per key or app, and 600 a minute for all of a company's
keys and apps together. X-RateLimit-Limit and X-RateLimit-Remaining are on
every response; a 429 carries Retry-After. Back off and retry with the same
Idempotency-Key on writes. An address that presents 20 bad keys within five
minutes is turned away (429) for the rest of the window.
Recipes
Keep payments in sync. Subscribe a webhook to the
payment.* events and apply each one, de-duplicating on the event id. Once a
day (and after any outage of your receiver), page through
GET /api/v1/payments?updatedSince=<your last high-water mark>&sort=updatedAt:asc
and upsert what you find: webhooks are fast, the list is the record. A
payment.deleted event is the only trace of a deleted row.
Retry writes safely. Give every POST an Idempotency-Key you derive from
your own record (invoice-8841, not a random value per attempt). A retry after
a timeout returns the first answer instead of creating a second row; a retry
that arrives while the first is still running gets 409 — wait and try again.
Try it on test data. Create a test key: with a sandbox provisioned it reads and writes the sandbox; without one it can only read production. Money moves on test data still need their action scopes.
Generate a contract. PUT /deals/{id}/signers, then
POST /deals/{id}/contracts { templateId } → 202. Listen for
contract.generated (it carries the draft signature request) or
contract.failed, then POST /esign-requests/{id}/send.
Versioning
The API is versioned in the path (/api/v1). Additive changes — new fields,
filters, endpoints, events — ship without notice and are listed in the changelog
below. Breaking changes ship as /api/v2 with at least 12 months of overlap,
and deprecated operations carry a Deprecation header during that time.
Examples
Create a lead from a website form (cURL):
curl -X POST https://acme.mcamgr.com/api/v1/leads \
-H "Authorization: Bearer mca_live_…" -H "Content-Type: application/json" \
-H "Idempotency-Key: form-8841" \
-d '{ "businessName": "Acme Diner LLC", "contactName": "Pat Owner", "email": "pat@example.com", "phone": "555-0100", "source": "website" }'
Page through funded deals (Node):
let cursor = null;
do {
const r = await fetch(`https://acme.mcamgr.com/api/v1/deals?state=FUNDED&limit=200${cursor ? `&cursor=${cursor}` : ""}`, { headers: { Authorization: `Bearer ${KEY}` } });
const page = await r.json();
for (const deal of page.deals) sync(deal);
cursor = page.nextCursor;
} while (cursor);
Mark a payment paid (Python):
import requests
r = requests.patch("https://acme.mcamgr.com/api/v1/payments/9001",
headers={"Authorization": f"Bearer {KEY}"}, json={"status": "PAID", "clearedDate": "2026-09-22"})
r.raise_for_status(); print(r.headers["X-Request-Id"], r.json()["payment"]["status"])
API changelog
- 1.5.0 (v737–v743, 2026-10-22)
- OAuth 2.0: client credentials, and authorization code with PKCE for apps acting for a user; tokens are accepted wherever a key is.
- Action scopes deals:fund, payments:settle and payments:refund, never implied by write access; keys are bounded by their creator's permissions.
- New endpoints: payment delete; deal statement, schedule health, fees, overpayment and close-out; merchant contacts, DBAs and masked bank accounts; people; signers and early delivery; contract generation (202 + webhook); document templates; e-sign requests; documents.
- Webhooks: a stable event id across endpoints and redeliveries; new events for deal updates and deletes, payment created/cancelled/deleted/refunded, bank-account changes, person merges, contracts and e-sign; OpenAPI
webhookssection. expand=is held to the key's scopes; errors carry codes that follow the status; Idempotency-Key claims before running;q=is rejected where a list has no search.- Rate limits: a ceiling for all of a company's keys together, a failed-authentication throttle per address, writes refused (503) when the limiter is down, Retry-After from the window.
- 1.4.0 (v630, 2026-09-24)
- OpenAPI generated from the code (every operation, real request/response schemas), full-method explorer with Postman export.
- 1.3.0 (v629, 2026-09-23)
- API keys: scopes, live/test kinds, expiry, IP allow-list, rotation;
GET /whoami.
- API keys: scopes, live/test kinds, expiry, IP allow-list, rotation;
- 1.2.0 (v628, 2026-09-23)
- Error envelope with
requestId,X-RateLimit-*, cursor pagination,sort/expand/fields/ids/date windows, per-resource filters,Idempotency-Key, decimal-string money; deal and payment writes through the shared services.
- Error envelope with
- 1.1.0 (v624–v626, 2026-09-22)
- Webhook envelope
{ id, event, version, occurredAt, data }, delivery log, retries and auto-pause,/webhook-endpointsmanagement; merchant contacts returnssnLast4only.
- Webhook envelope
Can’t find what you need? Return to the app or contact your administrator.