Documentation · Maqaas v1.0.0
Maqaas v1 — worked examples
All values below are synthetic. No real vendor, invoice, bank account, or
company appears in this file. Responses are abridged: rules_evaluated contains
every rule in a real response, not only the ones shown.
A. Clean invoice → PROCESS
Everything matches, every field verifies, no rule objects.
Request — POST /v1/invoice/decision
{
"client_reference": "ap-1001",
"message": { "from": "ar@northgate-fasteners.example", "subject": "Invoice NGF-2026-0188" },
"document": { "filename": "invoice.pdf", "media_type": "text/plain",
"text": "Northgate Fasteners Ltd\nInvoice No. NGF-2026-0188\nDate 2026-09-01\nPO-7741\nTOTAL DUE GBP 7,560.00" },
"po_context": { "po_number": "PO-7741", "total": { "amount": 7560, "currency": "GBP" }, "status": "open" },
"vendor_context": { "vendor_id": "V-200", "known_domains": ["northgate-fasteners.example"], "known_invoice_numbers": [] },
"rules": { "allowed_variance_pct": 0.05, "require_po": true }
}
Response — 200
{
"request_id": "req_4b91c2fe7a0d4e18b6c3",
"client_reference": "ap-1001",
"versions": { "api": "v1", "schema": "2026-09-06.v3", "rules": "1.3.0", "engine": "1.0.0",
"extraction": "2.0.0", "resolution_catalog": "1.4.0" },
"decision": "PROCESS",
"needs_review": false,
"recommended_route": null,
"decision_confidence": 1,
"confidence_basis": "weakest critical signal: invoice_number=1.00",
"document_type": "invoice",
"extracted_facts": {
"document_type": "invoice",
"classification_confidence": 1,
"vendor_name": { "value": "Northgate Fasteners Ltd", "model_confidence": 1, "source_verified": true,
"confidence": 1, "verification_reason": "exact",
"source": { "locator": "attachment:invoice.pdf", "snippet": "Northgate Fasteners Ltd" } },
"invoice_number": { "value": "NGF-2026-0188", "model_confidence": 1, "source_verified": true,
"confidence": 1, "verification_reason": "exact",
"source": { "locator": "attachment:invoice.pdf", "snippet": "Invoice No. NGF-2026-0188" } },
"invoice_total": { "value": { "amount": 7560, "currency": "GBP" }, "model_confidence": 1,
"source_verified": true, "confidence": 1, "verification_reason": "exact",
"source": { "locator": "attachment:invoice.pdf", "snippet": "TOTAL DUE GBP 7,560.00" } },
"po_number": { "value": "PO-7741", "model_confidence": 1, "source_verified": true, "confidence": 1,
"verification_reason": "exact", "source": { "locator": "attachment:invoice.pdf", "snippet": "PO-7741" } },
"vendor_id": { "value": null, "confidence": 0, "source_verified": null },
"bank_details": { "value": null, "confidence": 0, "source_verified": null },
"missing_fields": ["vendor_id", "bank_details"],
"conflicting_fields": []
},
"rules_fired": [],
"reasons": [],
"warnings_structured": [
{ "code": "MISSING_FIELD", "category": "data_quality", "field": "vendor_id", "rule_id": null,
"detail": null, "message": "vendor_id was not present in the supplied input." },
{ "code": "RULE_NOT_EVALUATED", "category": "data_quality", "field": null, "rule_id": "BANK_DETAILS_CHANGED",
"detail": "no payment details found on the invoice",
"message": "BANK_DETAILS_CHANGED could not be evaluated: no payment details found on the invoice" }
],
"resolution": {
"catalog_version": "1.4.0",
"blocking_conditions": [],
"resolution_requirements": [],
"recommended_action": "NONE",
"resolution_plan": [],
"decision_if_resolved": null
},
"extraction": { "mode": "model", "model_calls": 1, "latency_ms": 794 },
"processed_at": "2026-09-08T10:14:02.881Z"
}
resolution is always present, including on a clean PROCESS. Nothing is
blocking, so every list is empty and recommended_action is NONE. Switch on
decision first; read resolution when the decision is not PROCESS.
Note that PROCESS still reports what could not be checked. BANK_DETAILS_CHANGED
was skipped because the invoice states no payment details — that is uncertainty,
not a pass.
B. Review case → REVIEW (unverified identifier)
The invoice number was read as NGF-2026-01?8 — one character is a placeholder.
The value is not repaired; it is flagged and routed.
Response — 200 (abridged)
{
"request_id": "req_7c02fa5d13b84a9e0f77",
"decision": "REVIEW",
"needs_review": true,
"recommended_route": "accounts_payable_clerk",
"decision_confidence": 0.5,
"confidence_basis": "weakest critical signal: invoice_number=0.50",
"extracted_facts": {
"invoice_number": {
"value": "NGF-2026-01?8",
"model_confidence": 0.92,
"source_verified": false,
"confidence": 0.5,
"verification_reason": "identifier_contains_placeholder",
"source": { "locator": "attachment:invoice.pdf", "snippet": "Invoice No. NGF-2026-01?8" }
}
},
"rules_fired": [
{ "rule_id": "LOW_EXTRACTION_CONFIDENCE", "rule_version": "1.0.0", "status": "fired", "outcome": "fired",
"decision": "REVIEW", "severity": 40, "fields": ["invoice_number"],
"message": "Extraction confidence below the 0.8 floor for: invoice_number=0.50.",
"evidence": { "inputs": { "invoice_number_confidence": 0.5, "invoice_total_confidence": 1, "vendor_name_confidence": 1 },
"threshold": 0.8, "computed": 1, "comparison": "1 critical field(s) below 0.8", "sources": [] } },
{ "rule_id": "UNVERIFIED_IDENTIFIER", "rule_version": "1.0.0", "status": "fired", "outcome": "fired",
"decision": "REVIEW", "severity": 40, "fields": ["invoice_number"],
"message": "Identifier present but not supported by the document: invoice_number (identifier_contains_placeholder, confidence 0.50). The value has not been altered; a human should confirm it.",
"evidence": { "inputs": { "fields": "invoice_number", "reasons": "invoice_number=identifier_contains_placeholder", "identifiers_checked": 5 },
"threshold": "source_verified must not be false", "computed": 1,
"comparison": "1 identifier(s) present but unverified", "sources": [] } }
],
"warnings_structured": [
{ "code": "UNVERIFIED_FIELD", "category": "decision_affecting", "field": "invoice_number", "rule_id": null,
"detail": "identifier_contains_placeholder",
"message": "invoice_number could not be confirmed against the document (identifier_contains_placeholder)." }
]
}
C. Bank details changed → HOLD_FOR_REVIEW
The canonical case. The invoice states payment details that differ from the vendor record you sent. This is the highest-value fraud signal in accounts payable, so it holds rather than reviews.
Both blocks below are the real request and response, captured from
POST /v1/invoice/decision. Only request_id and processed_at are
substituted, and rules_evaluated is abridged to its outcomes.
Request
{
"client_reference": "ap-8891",
"extracted_facts": {
"document_type": "invoice",
"classification_confidence": 0.99,
"vendor_name": { "value": "Acme Supplies Ltd", "confidence": 0.95 },
"vendor_id": { "value": "V-100", "confidence": 0.95 },
"vat_number": { "value": null, "confidence": 0 },
"company_registration_number": { "value": null, "confidence": 0 },
"invoice_number": { "value": "INV-2026-0417", "confidence": 0.95 },
"invoice_total": { "value": { "amount": 41200, "currency": "GBP" }, "confidence": 0.95 },
"tax_total": { "value": { "amount": 0, "currency": "GBP" }, "confidence": 0.95 },
"invoice_date": { "value": "2026-08-04", "confidence": 0.95 },
"po_number": { "value": "PO-500", "confidence": 0.95 },
"bank_details": {
"value": { "account_number_last4": "4417", "sort_code": "60-84-12", "bank_name": "Northern Trust Commercial" },
"confidence": 0.95,
"source": { "locator": "attachment:invoice.pdf", "snippet": "Sort Code: 60-84-12 Account ending 4417" }
},
"missing_fields": [],
"conflicting_fields": []
},
"po_context": { "po_number": "PO-500", "total": { "amount": 41200, "currency": "GBP" }, "vendor_id": "V-100", "status": "open" },
"vendor_context": {
"vendor_id": "V-100",
"legal_name": "Acme Supplies Ltd",
"known_domains": ["acme-supplies.com"],
"known_invoice_numbers": ["INV-2026-0388"],
"trusted_bank_details": { "account_number_last4": "9021", "sort_code": "20-00-00", "bank_name": "Barclays Commercial" }
},
"rules": {
"allowed_variance_pct": 0.05, "require_po": true, "bank_change_requires_review": true,
"min_field_confidence": 0.8, "require_currency_match": true
}
}
This example sends every field for completeness, but it does not have to. Caller-supplied facts may be partial: only
document_typeis required. Omitted fields are materialised internally as{ "value": null, "confidence": 0, "source_verified": null }, and are not added tomissing_fields— that list means the extractor looked and could not find the value. Fields you do send are validated exactly as normal. See §4 of API_V1.md.
Response — 200
{
"request_id": "req_3f9a2c10b7e44d0f8c1e5a7b",
"client_reference": "ap-8891",
"versions": {
"api": "v1", "schema": "2026-09-06.v3", "rules": "1.3.0",
"engine": "1.0.0", "extraction": "2.0.0", "resolution_catalog": "1.4.0"
},
"decision": "HOLD_FOR_REVIEW",
"needs_review": true,
"recommended_route": "accounts_payable_manager",
"decision_confidence": 0.95,
"confidence_basis": "weakest critical signal: invoice_number=0.95",
"rules_fired": [
{
"rule_id": "BANK_DETAILS_CHANGED",
"rule_version": "1.2.0",
"outcome": "fired", "status": "fired",
"decision": "HOLD_FOR_REVIEW", "severity": 70,
"fields": ["bank_details.account_number_last4", "bank_details.sort_code"],
"message": "Payment details differ from the trusted vendor record (account_number_last4, sort_code). Possible invoice redirection fraud.",
"evidence": {
"inputs": {
"vendor_id": "V-100",
"compared_fields": "account_number_last4, sort_code",
"changed_fields": "account_number_last4, sort_code",
"fields": "bank_details.account_number_last4, bank_details.sort_code",
"expected": "account_number_last4=9021, sort_code=20-00-00",
"observed": "account_number_last4=4417, sort_code=60-84-12",
"bank_name_differs": true,
"extraction_confidence": 0.95,
"confidence_floor": 0.8,
"source_verified": null,
"confirmed": true
},
"threshold": "any normalised identifier differs",
"computed": 2,
"comparison": "2 of 2 compared identifier(s) differ from the trusted record (normalised); bank_name_differs=true",
"sources": [{ "locator": "attachment:invoice.pdf", "snippet": "Sort Code: 60-84-12 Account ending 4417" }]
},
"skipped_reason": null
}
],
"resolution": {
"catalog_version": "1.4.0",
"blocking_conditions": [
{
"rule_id": "BANK_DETAILS_CHANGED",
"type": "bank_details_changed",
"severity": "HOLD_FOR_REVIEW",
"fields": ["bank_details.account_number_last4", "bank_details.sort_code"],
"message": "Payment details differ from the trusted vendor record (account_number_last4, sort_code). Possible invoice redirection fraud.",
"evidence": {
"expected": "account_number_last4=9021, sort_code=20-00-00",
"observed": "account_number_last4=4417, sort_code=60-84-12",
"comparison": "2 of 2 compared identifier(s) differ from the trusted record (normalised); bank_name_differs=true"
}
}
],
"resolution_requirements": [
{
"rule_id": "BANK_DETAILS_CHANGED",
"requirement_type": "independent_bank_verification",
"acceptable_evidence": [
"approved_vendor_master_update",
"verification_via_previously_trusted_supplier_contact",
"signed_bank_change_authorisation_on_vendor_letterhead"
],
"instruction": "Confirm the new payment details through a channel independent of this invoice before paying. Do not use contact details taken from the invoice itself."
}
],
"recommended_action": "REQUEST_VENDOR_BANK_VERIFICATION",
"resolution_plan": [
{
"step": 1, "rule_id": "BANK_DETAILS_CHANGED",
"action": "REQUEST_VENDOR_BANK_VERIFICATION",
"owner": "VENDOR_MANAGEMENT",
"requirement_type": "independent_bank_verification",
"fields": ["bank_details.account_number_last4", "bank_details.sort_code"],
"instruction": "Confirm the new payment details through a channel independent of this invoice before paying. Do not use contact details taken from the invoice itself."
},
{
"step": 2, "rule_id": null,
"action": "RESUBMIT_FOR_DECISION", "owner": "HOST_SYSTEM",
"requirement_type": "decision_re_evaluation", "fields": [],
"instruction": "Call the decision endpoint again with the updated context. `decision_if_resolved` states what the decision becomes once every step above is cleared and nothing else changes; supplying new context may activate rules listed in rules_still_not_evaluated."
}
],
"decision_if_resolved": {
"decision": "PROCESS",
"conditional": true,
"assumption": {
"resolved_rule_ids": ["BANK_DETAILS_CHANGED"],
"statement": "Assumes this condition is cleared. No facts were substituted, corrected, or invented. 2 rules could not be evaluated for lack of context and are assumed not to object; see rules_still_not_evaluated."
},
"rules_still_not_evaluated": [
{ "rule_id": "APPROVAL_THRESHOLD", "reason": "approval_threshold_not_configured" },
{ "rule_id": "VENDOR_IDENTITY_MISMATCH", "reason": "vendor_domain_or_sender_not_supplied" }
],
"remaining_rules_fired": [],
"caveat": "Deterministic result with the named blockers set aside and all other supplied facts unchanged. Supplying new context to clear a blocker may activate rules listed in rules_still_not_evaluated."
}
},
"extraction": { "mode": "caller_supplied", "model_calls": 0, "latency_ms": null },
"processed_at": "2026-09-11T09:14:22.411Z"
}
How to read this
- Stop the payment.
decisionisHOLD_FOR_REVIEWandneeds_reviewistrue. Route toaccounts_payable_manager. - Show the reviewer the evidence.
expectedvsobservednames both values;sourcesgives the locator and the snippet they came from. - Act on
recommended_action, not on the prose. It is a closed enum:REQUEST_VENDOR_BANK_VERIFICATION, owned byVENDOR_MANAGEMENT. decision_if_resolved.conditionalistrue—PROCESShere means “nothing that could be evaluated objects”, not “safe to pay”. Two rules could not run. Supply anapproval_thresholdandmessage.fromand they will be evaluated on the next call.- Re-submit once the requirement is met. Nothing about the bank details is changed by Maqaas; you update your own vendor master.
If the payment identifiers match and only bank_name is written differently,
the rule passes and reports bank_name_differs: true in evidence. If you
supply no trusted_bank_details at all, see section D.
D. No trusted vendor bank record → REVIEW
The vendor is known, but your master record carries no
trusted_bank_details. The invoice does state payment details.
This is the case where an API can most easily lie. There is nothing to compare
against, so Maqaas must not report a comparison — and it must not stay silent
either, because paying an unverified account is the exact failure this product
exists to prevent. It fires the same rule at the lower REVIEW severity and
says why in evidence: computed: "no_baseline".
Both blocks below are the real request and response from
POST /v1/invoice/decision. Only request_id and processed_at are
substituted, and rules_evaluated is omitted.
Request
{
"client_reference": "ap-9107",
"extracted_facts": {
"document_type": "invoice",
"classification_confidence": 0.99,
"vendor_name": { "value": "Meridian Facilities Ltd", "confidence": 0.95 },
"vendor_id": { "value": "V-420", "confidence": 0.95 },
"vat_number": { "value": null, "confidence": 0 },
"company_registration_number": { "value": null, "confidence": 0 },
"invoice_number": { "value": "INV-4471", "confidence": 0.95 },
"invoice_total": { "value": { "amount": 8750, "currency": "GBP" }, "confidence": 0.95 },
"tax_total": { "value": { "amount": 0, "currency": "GBP" }, "confidence": 0.95 },
"invoice_date": { "value": "2026-09-02", "confidence": 0.95 },
"po_number": { "value": "PO-771", "confidence": 0.95 },
"bank_details": {
"value": { "account_number_last4": "3318", "sort_code": "04-00-75", "bank_name": "Monzo Business" },
"confidence": 0.95,
"source": { "locator": "attachment:invoice.pdf", "snippet": "Sort Code: 04-00-75 Account ending 3318" }
},
"missing_fields": [],
"conflicting_fields": []
},
"po_context": { "po_number": "PO-771", "total": { "amount": 8750, "currency": "GBP" }, "vendor_id": "V-420", "status": "open" },
"vendor_context": {
"vendor_id": "V-420",
"legal_name": "Meridian Facilities Ltd",
"known_domains": ["meridian-fm.co.uk"],
"known_invoice_numbers": []
},
"rules": {
"allowed_variance_pct": 0.05, "require_po": true, "bank_change_requires_review": true,
"min_field_confidence": 0.8, "require_currency_match": true
}
}
Response — 200
{
"request_id": "req_58df8356db464c3eb2af43da",
"client_reference": "ap-9107",
"versions": {
"api": "v1", "schema": "2026-09-06.v3", "rules": "1.3.0",
"engine": "1.0.0", "extraction": "2.0.0", "resolution_catalog": "1.4.0"
},
"decision": "REVIEW",
"needs_review": true,
"recommended_route": "accounts_payable_clerk",
"decision_confidence": 0.95,
"confidence_basis": "weakest critical signal: invoice_number=0.95",
"document_type": "invoice",
"rules_fired": [
{
"rule_id": "BANK_DETAILS_CHANGED",
"rule_version": "1.2.0",
"outcome": "fired", "status": "fired",
"decision": "REVIEW",
"severity": 40,
"fields": [],
"message": "Invoice carries payment details but no trusted vendor bank record exists to compare against.",
"evidence": {
"inputs": { "has_trusted_record": false, "fields": null },
"threshold": "bank_change_requires_review=true",
"computed": "no_baseline",
"comparison": "trusted_bank_details is absent",
"sources": [
{ "locator": "attachment:invoice.pdf", "snippet": "Sort Code: 04-00-75 Account ending 3318" }
]
},
"skipped_reason": null
}
],
"reasons": [
"Invoice carries payment details but no trusted vendor bank record exists to compare against."
],
"warnings_structured": [
{ "code": "RULE_NOT_EVALUATED", "category": "data_quality", "field": null,
"rule_id": "DUPLICATE_INVOICE_NUMBER", "detail": "no invoice history supplied for this vendor",
"message": "DUPLICATE_INVOICE_NUMBER could not be evaluated: no invoice history supplied for this vendor" },
{ "code": "RULE_NOT_EVALUATED", "category": "data_quality", "field": null,
"rule_id": "APPROVAL_THRESHOLD", "detail": "no approval_threshold configured",
"message": "APPROVAL_THRESHOLD could not be evaluated: no approval_threshold configured" },
{ "code": "RULE_NOT_EVALUATED", "category": "data_quality", "field": null,
"rule_id": "VENDOR_IDENTITY_MISMATCH", "detail": "no sender address supplied",
"message": "VENDOR_IDENTITY_MISMATCH could not be evaluated: no sender address supplied" }
],
"missing_fields": [],
"resolution": {
"catalog_version": "1.4.0",
"blocking_conditions": [
{
"rule_id": "BANK_DETAILS_CHANGED",
"type": "bank_details_changed",
"severity": "REVIEW",
"fields": [],
"message": "Invoice carries payment details but no trusted vendor bank record exists to compare against.",
"evidence": {
"expected": "bank_change_requires_review=true",
"observed": "no_baseline",
"comparison": "trusted_bank_details is absent"
}
}
],
"resolution_requirements": [
{
"rule_id": "BANK_DETAILS_CHANGED",
"requirement_type": "independent_bank_verification",
"acceptable_evidence": [
"approved_vendor_master_update",
"verification_via_previously_trusted_supplier_contact",
"signed_bank_change_authorisation_on_vendor_letterhead"
],
"instruction": "Confirm the new payment details through a channel independent of this invoice before paying. Do not use contact details taken from the invoice itself."
}
],
"recommended_action": "REQUEST_VENDOR_BANK_VERIFICATION",
"resolution_plan": [
{
"step": 1, "rule_id": "BANK_DETAILS_CHANGED",
"action": "REQUEST_VENDOR_BANK_VERIFICATION",
"owner": "VENDOR_MANAGEMENT",
"requirement_type": "independent_bank_verification",
"fields": [],
"instruction": "Confirm the new payment details through a channel independent of this invoice before paying. Do not use contact details taken from the invoice itself."
},
{
"step": 2, "rule_id": null,
"action": "RESUBMIT_FOR_DECISION", "owner": "HOST_SYSTEM",
"requirement_type": "decision_re_evaluation", "fields": [],
"instruction": "Call the decision endpoint again with the updated context. `decision_if_resolved` states what the decision becomes once every step above is cleared and nothing else changes; supplying new context may activate rules listed in rules_still_not_evaluated."
}
],
"decision_if_resolved": {
"decision": "PROCESS",
"conditional": true,
"assumption": {
"resolved_rule_ids": ["BANK_DETAILS_CHANGED"],
"statement": "Assumes this condition is cleared. No facts were substituted, corrected, or invented. 3 rules could not be evaluated for lack of context and are assumed not to object; see rules_still_not_evaluated."
},
"rules_still_not_evaluated": [
{ "rule_id": "DUPLICATE_INVOICE_NUMBER", "reason": "vendor_invoice_history_not_supplied" },
{ "rule_id": "APPROVAL_THRESHOLD", "reason": "approval_threshold_not_configured" },
{ "rule_id": "VENDOR_IDENTITY_MISMATCH", "reason": "vendor_domain_or_sender_not_supplied" }
],
"remaining_rules_fired": [],
"caveat": "Deterministic result with the named blockers set aside and all other supplied facts unchanged. Supplying new context to clear a blocker may activate rules listed in rules_still_not_evaluated."
}
},
"extraction": { "mode": "caller_supplied", "model_calls": 0, "latency_ms": null },
"processed_at": "2026-09-11T13:49:15.826Z"
}
How to read this
fieldsis empty, and that is the point. No identifier was compared, so the rule names none. Contrast section C, wherefieldslists the two identifiers that actually differ.computedisno_baseline, not a value.comparisonsaystrusted_bank_details is absentin plain terms. Nothing in the response claims the invoice bank details were checked against anything.- Severity is
REVIEW, notHOLD_FOR_REVIEW. A missing baseline is missing information, not evidence of a change. The invoice still stops —needs_reviewistrueeither way. - The remedy is the same.
REQUEST_VENDOR_BANK_VERIFICATIONowned byVENDOR_MANAGEMENT: verify the account out-of-band, then populate your vendor master so the next invoice has a baseline. - Three rules could not run, each named in
warnings_structuredwith a reason. An unevaluated safety rule is reported as uncertainty, never silently treated as a pass.
E. Missing PO → REQUEST_MORE_INFORMATION
Your policy requires a PO and neither the invoice nor the request supplies one.
Request fragment
{ "rules": { "require_po": true } }
Response — 200 (abridged)
{
"decision": "REQUEST_MORE_INFORMATION",
"needs_review": true,
"recommended_route": "accounts_payable_clerk",
"rules_fired": [
{ "rule_id": "MISSING_PO", "rule_version": "1.0.0", "status": "fired", "outcome": "fired",
"decision": "REQUEST_MORE_INFORMATION", "severity": 50, "fields": [],
"message": "Customer policy requires a purchase order, but no PO number was found on the invoice or supplied as context.",
"evidence": { "inputs": { "po_number_on_invoice": null, "po_number_in_context": null },
"threshold": "require_po=true", "computed": null,
"comparison": "po_number is null", "sources": [] } }
]
}
Maqaas does not contact the vendor. REQUEST_MORE_INFORMATION tells your
system that input is missing; chasing it is your workflow.
F. Error — scanned PDF
Response — 422
{
"error": {
"code": "OCR_REQUIRED_NOT_AVAILABLE",
"message": "This document appears to be a scan and requires OCR, which is not enabled.",
"request_id": "req_1a4d90c7e5b2483fa6d0",
"retryable": false,
"details": [{ "path": "document", "message": "OCR_REQUIRED_NOT_AVAILABLE" }]
}
}
retryable: false — resubmitting the same scan will fail identically. Route it
to manual entry instead.
G. Uploading a document — the complete call
Sections A–F post already-extracted facts or inline text. This is the other transport: you hand Maqaas the file itself and it does the extraction.
The endpoint is POST /v1/invoice/decision/document. The response contract is
identical to the JSON route — same decision, same extracted_facts, same
resolution — plus one extra ingestion block describing how the file was
read.
Prepare the document. content_base64 is the raw file, base64-encoded, with
no data-URI prefix and no line wrapping:
# Linux
base64 -w0 invoice.pdf > invoice.b64
# macOS
base64 -i invoice.pdf -o invoice.b64
-w0 is a GNU option and is not available on macOS, which is why the two
commands differ. Both write the same thing: one unwrapped line in
invoice.b64.
Send it. The request reads that file, so this call is identical on either platform:
curl -sS https://api.maqaas.com/v1/invoice/decision/document \
-H "Authorization: Bearer $MAQAAS_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ap-2031-attempt-1" \
-d @- <<JSON
{
"client_reference": "ap-2031",
"document": {
"filename": "invoice.pdf",
"media_type": "application/pdf",
"content_base64": "$(cat invoice.b64)"
},
"po_context": { "po_number": "PO-7741", "total": { "amount": 7560, "currency": "GBP" }, "status": "open" },
"rules": { "allowed_variance_pct": 0.05, "require_po": true }
}
JSON
Never put a real key in a file you commit — read it from the environment, as above.
The request shape, if you are building it in code rather than curl. This one is complete and runnable as written — the document is a short plain-text invoice rather than a PDF, so the base64 fits on the page:
{
"client_reference": "ap-2031",
"document": {
"filename": "invoice.txt",
"media_type": "text/plain",
"content_base64": "Tm9ydGhnYXRlIEZhc3RlbmVycyBMdGQKSW52b2ljZSBOby4gTkdGLTIwMjYtMDE4OApQTy03NzQxClRPVEFMIERVRSBHQlAgNyw1NjAuMDA="
},
"po_context": { "po_number": "PO-7741", "total": { "amount": 7560, "currency": "GBP" }, "status": "open" },
"rules": { "allowed_variance_pct": 0.05, "require_po": true }
}
For a PDF, swap filename, set media_type to application/pdf, and put the
output of base64 -w0 invoice.pdf in content_base64. Nothing else changes.
document is the only required member. media_type may be omitted — it is
inferred from the file’s magic bytes, and a mismatch between the declared type
and the actual bytes is rejected rather than guessed.
What comes back that the JSON route does not send:
{
"ingestion": {
"filename": "invoice.pdf",
"media_type": "application/pdf",
"extraction_method": "native_pdf",
"page_count": 2,
"extraction_ms": 118,
"warnings": []
}
}
extraction_method is one of plain_text, native_pdf or ocr. A scan Maqaas
cannot read never returns a decision — it returns 422 OCR_REQUIRED_NOT_AVAILABLE instead, as in section F. Accepted types are
application/pdf and text/plain; limits are 10 MB decoded and 30 pages, in
PILOT_SECURITY.md.