ISO360 API
A REST API for ISO360 agent offices. Push a merchant statement and get a rate analysis back as a proposal in your office; read your boarding applications. One bearer key per office, JSON in and out, over HTTPS.
Introduction
Every ISO360 API call is authenticated with a bearer key that belongs to a single agent office. The key carries the office identity, so you never pass an office or agent id in a request – whatever you create lands under the office that owns the key.
- Base URL:
https://www.cygma.cloud/api/v1 - Auth:
Authorization: Bearer <key>on every request. - Format: JSON responses; uploads use
multipart/form-data.
Build against merchants, applications, proposals, residuals, statements, the equipment catalog, and the reference lists on a sandbox office with demo merchants. No office agreement needed.
Open the developer sandboxAuthentication
Generate your key in ISO360 under My Office → ISO360 API. The full secret is shown once at generation – store it somewhere safe; it can’t be retrieved again. Regenerating immediately invalidates the previous key.
- Send it as
Authorization: Bearer <key>. Treat it like a password – server-side only, never in client code. - Each key is scoped (e.g.
proposals:create) and rate-limited per minute. Exceeding the limit returns429. - Lost or leaked a key? Regenerate it – the old one stops working instantly.
No office yet? Grab a self-serve read-only key from the sandbox and build against demo merchants first.
# Every request carries your office key as a bearer token. curl https://www.cygma.cloud/api/v1/applications \ -H "Authorization: Bearer epi_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Developer sandbox
Request a key at www.cygma.cloud/iso360/sandbox. It is issued instantly and bound to a shared sandbox office (SANDBOX360) that owns three demo merchants, so every read returns a realistic shape without touching a real office’s book.
- Read-only. Granted scopes are
merchants:read,applications:read,proposals:read,residuals:read,statements:read,orders:read,reference:read, andwebinars:read. - No writes at all. Boarding, change requests, terminal provisioning, and orders need write scopes EPI grants to a real office. A sandbox key gets
403on those. - Capped. Five active keys per email address, and a global daily ceiling. Lost a key? Request another.
# Request a sandbox key (no auth). One call, key comes back once.
curl -X POST https://www.cygma.cloud/api/v1/sandbox/keys \
-H "Content-Type: application/json" \
-d '{ "email": "you@company.com", "company": "Acme Payments" }'
# Then use it like any office key.
curl https://www.cygma.cloud/api/v1/merchants \
-H "Authorization: Bearer <your-sandbox-key>"Quickstart
- Create your office key in My Office → ISO360 API.
- POST a statement PDF (plus the merchant name) to Analyze a statement. The response carries the parsed numbers and a link to the proposal it created under your office.
# 1. Grab your office key from My Office → ISO360 API. # 2. Push a statement and read the analysis back. curl -X POST https://www.cygma.cloud/api/v1/proposals/analyze-statement \ -H "Authorization: Bearer <your-key>" \ -F "statement=@statement.pdf;type=application/pdf" \ -F "merchant_name=Jane's Coffee LLC" \ -F "mcc=5812"
Analyze a statement
Uploads a merchant’s prior-processing statement and returns a rate analysis. The call also creates a lead and a draft proposal in the office that owns the key, so the analysis is waiting in ISO360 when your agent logs in.
Request – multipart/form-data:
| Field | Required | Notes |
|---|---|---|
| statement | yes | PDF file, 25 MB max. |
| merchant_name | yes | Business name. |
| mcc | no | 4-digit MCC. Inferred when omitted. |
| address, city, state, zip | no | Physical address. Blanks are backfilled from the statement. |
| dba_name, legal_name, phone, email | no | Contact details for the lead. |
Pass an Idempotency-Key header to make retries safe: the same key returns the proposal created the first time instead of making a duplicate.
The call is synchronous and can take 15–30 seconds while the statement is read. If the statement can’t be parsed you get a 422 (the draft proposal is still created so the agent can retry in-app).
curl -X POST https://www.cygma.cloud/api/v1/proposals/analyze-statement \ -H "Authorization: Bearer <your-key>" \ -H "Idempotency-Key: 7f3c1b9e-…" \ -F "statement=@statement.pdf;type=application/pdf" \ -F "merchant_name=Jane's Coffee LLC" \ -F "mcc=5812" \ -F "address=123 Example St" \ -F "city=Anytown" -F "state=ID" -F "zip=83702" \ -F "email=owner@example.com"
Proposals lifecycle
Build a pricing proposal end-to-end: open a draft (anchored to a merchant or a lead) → save a priced version from pricing knobs → generate the PDF → send it → mark accepted. Writes need proposals:write; reads need proposals:read. The acting user is the key’s creator, and every write reuses the same validation the in-app builder runs – including the office’s pricing limits on a version save.
Endpoints:
POST /proposals– open a draft (merchant_idorlead_id).GET /proposals·GET /proposals/{id}– list / fetch.POST /proposals/{id}/versions– save a priced version (knobs.pricing_modelrequired).POST /proposals/{id}/pdf– generate the merchant PDF;GETthe same path downloads it.POST /proposals/{id}/send– email it (to_email) and markSENT.POST /proposals/{id}/accept– markACCEPTED.
| Field | Type | Description |
|---|---|---|
merchant_idoptional | string | Anchor to an existing merchant. Provide this OR lead_id (exactly one). |
lead_idoptional | string | Anchor to a lead instead of a merchant. |
target_savings_pctoptional | number | Optional savings target to prompt the pricing engine. |
target_savings_dollarsoptional | number | Optional dollar savings target. |
| Field | Type | Description |
|---|---|---|
knobsrequired | object | Pricing knobs. At minimum pricing_model (IC_PLUS | FLAT | CASH_DISCOUNT | SURCHARGE) plus the rate fields (e.g. discount_rate_bps, per_auth_cents, monthly_fee_cents). Validated against your office’s pricing limits. |
knobs (per card type)optional | number | Optional. IC+: amex_discount_rate_bps, pin_debit_discount_rate_bps. Flat: flat_amex_rate_pct, flat_pin_debit_rate_pct. Both: amex_per_auth_cents, pin_debit_per_auth_cents. Surcharge: surcharge_debit_rate_pct, surcharge_debit_per_auth_cents (debit is not surcharged). Leave a field out to use the main rate. |
current_processingoptional | object | Optional statement facts for the savings math: annual_volume, avg_ticket, effective_rate_pct, annual_fees, processor, card mix (pct_visa_mc, pct_amex, pct_debit, pct_pin_debit). |
modeoptional | string | create (default, new version) | update_latest (overwrite the latest). |
| Field | Type | Description |
|---|---|---|
to_emailrequired | string | Recipient for the merchant-facing PDF. Marks the proposal SENT. |
The response and the PDF carry the merchant-facing pricing figures.
# 1. open a draft against a merchant (or a lead)
curl -X POST "https://www.cygma.cloud/api/v1/proposals" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "merchant_id": "<merchant-id>", "target_savings_pct": 15 }'
# 2. save a priced version
curl -X POST "https://www.cygma.cloud/api/v1/proposals/<id>/versions" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "knobs": { "pricing_model": "IC_PLUS", "discount_rate_bps": 25, "per_auth_cents": 10 },
"current_processing": { "annual_volume": 1200000, "avg_ticket": 42, "effective_rate_pct": 3.1 } }'
# 3. generate the PDF, then 4. send it
curl -X POST "https://www.cygma.cloud/api/v1/proposals/<id>/pdf" -H "Authorization: Bearer <your-key>"
curl -X POST "https://www.cygma.cloud/api/v1/proposals/<id>/send" -H "Authorization: Bearer <your-key>" \
-H "Content-Type: application/json" -d '{ "to_email": "owner@merchant.com" }'Board a merchant
Board a merchant end-to-end. POST /applications (applications:write) opens a draft merchant + application from a structured body – business identity, owners (SSN/DOB/EIN are encrypted at rest), banking, risk, and an optional pricing template – attributed to your office. Target NORTH / OMAHA (Fiserv) or CYGMA.
“Required” below means required for this call to succeed. Fields marked (to submit) are optional at create but must be present before POST /applications/{id}/submit passes – send them now or via PATCH. Run /preflight any time to see exactly what’s still missing.
| Field | Type | Description |
|---|---|---|
businessrequired | object | Business identity block (see below). |
preferred_backendoptional | string | Processor to board on: NORTH (default), OMAHA, or CYGMA. |
ownersoptional | object[] | Beneficial owners / signers. (≥1 required to submit.) |
bankoptional | object | Deposit account. (Required to submit.) |
riskoptional | object | Processing volumes + sales mix (feeds underwriting). |
pricing_template_idoptional | string | Attach a rate card from GET /pricing-templates. The id is shown on each template in ISO360 (Pricing Templates) with a copy-to-clipboard API chip. |
equipment_template_idoptional | string | Attach a saved equipment bundle from GET /equipment-templates (materializes a draft order with the template lines). The id is shown on each template in ISO360 (Equipment Templates) with a copy-to-clipboard API chip. |
| Field | Type | Description |
|---|---|---|
dba_namerequired | string | Doing-business-as name. |
legal_namerequired | string | Legal entity name (W-9 line 1). |
addressrequired | string | Physical / DBA street address. |
cityrequired | string | DBA city. |
staterequired | string | 2-letter state. |
ziprequired | string | DBA ZIP. |
business_start_dateoptional | string | YYYY-MM-DD. (Required to submit.) |
mccoptional | string | 4-digit merchant category code. (Required to provision a Cygma TID.) Look one up via /reference/mcc. |
einoptional | string | Federal Tax ID / EIN. Encrypted at rest; only last-4 is ever returned. |
tin_typeoptional | string | SSN | EIN | ITIN. |
tax_classificationoptional | string | Entity type – see /reference/business-types. |
business_typeoptional | string | Free-text business description. |
phoneoptional | string | DBA phone. |
emailoptional | string | Merchant contact email (lowercased). |
websiteoptional | string | Merchant website. |
| Field | Type | Description |
|---|---|---|
full_namerequired | string | Owner's full legal name. |
ownership_pctoptional | number | Percent ownership. Owners ≥25% must have SSN, DOB, and full address to submit. |
ssnoptional | string | 9-digit SSN. Encrypted at rest. (Required to submit for owners ≥25% + control persons.) |
doboptional | string | YYYY-MM-DD. Encrypted at rest. (Required to submit, same rule.) |
address / city / state / zipoptional | string | Owner home address. (Required to submit, same rule.) |
is_signeroptional | boolean | Marks a signing owner. Signers must total ≥50% equity to submit (auto-promoted if short). |
is_control_onlyoptional | boolean | FinCEN control person with no equity – still needs SSN/DOB/address. |
titleoptional | string | Owner title (e.g. President). |
email / phoneoptional | string | Owner contact. |
| Field | Type | Description |
|---|---|---|
routingoptional | string | 9-digit ABA routing. (Required to submit.) Validate via /banking/routing. |
accountoptional | string | Deposit account number. (Required to submit.) |
account_typeoptional | string | CHECKING (default) | SAVINGS. |
bank_nameoptional | string | Bank name (recommended). |
| Field | Type | Description |
|---|---|---|
annual_volumeoptional | number | Annual card volume, dollars. |
average_ticketoptional | number | Average sale, dollars. |
high_ticketoptional | number | Highest single sale, dollars. |
pct_swiped / pct_keyed / pct_moto / pct_internetoptional | number | Entry mix %, should total 100. |
pct_retail / pct_b2b / pct_mobileoptional | number | Sales-location mix %. |
| Field | Type | Description |
|---|---|---|
signature_namerequired | string | The signer's typed name (the e-signature). |
signature_bloboptional | string | Optional signature image as a data URL. |
confirmation_signature_nameoptional | string | Defaults to signature_name. |
confirmation_signature_bloboptional | string | Defaults to signature_blob. |
POST /applications/{id}/preflight tells you exactly what still blocks a submit (the same completeness gate the app enforces), plus the backend’s own requirements – the Fiserv MPA missing[] for NORTH/OMAHA (most of which are completed downstream at File Build), or the MID + 4-digit MCC note for Cygma (which provisions its TID after approval).
POST /applications/{id}/submit executes the signed application and kicks off underwriting (auto due-diligence, GIACT, OFAC, risk scoring). Supply the signer’s name; a 422 returns the reason if the gate isn’t satisfied.
You don’t have to send everything at once – PATCH /applications/{id} fills a DRAFT iteratively (same body, only the fields you send change; owners replaces the list). Set the FinCEN control_question_answered flag here too. Editing is DRAFT-only.
Cygma go-live (MID assignment + TID provisioning) happens on EPI’s side after approval. Owner + bank data must be present before submit.
# 1. open a draft application (NORTH / OMAHA / CYGMA)
curl -X POST "https://www.cygma.cloud/api/v1/applications" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{
"preferred_backend": "CYGMA",
"business": { "dba_name": "Joe\'s Diner", "legal_name": "Joe Diner LLC",
"address": "1 Main St", "city": "Austin", "state": "TX", "zip": "78701",
"mcc": "5812", "business_start_date": "2019-03-01", "ein": "12-3456789", "tin_type": "EIN" },
"owners": [ { "full_name": "Joe Smith", "ownership_pct": 100, "ssn": "123-45-6789",
"dob": "1980-01-01", "address": "1 Main St", "city": "Austin", "state": "TX", "zip": "78701",
"is_signer": true } ],
"bank": { "routing": "021000021", "account": "123456789", "account_type": "CHECKING" },
"risk": { "annual_volume": 1200000, "average_ticket": 42, "high_ticket": 500 }
}'
# 2. check readiness 3. execute (e-sign)
curl -X POST "https://www.cygma.cloud/api/v1/applications/<id>/preflight" -H "Authorization: Bearer <your-key>"
curl -X POST "https://www.cygma.cloud/api/v1/applications/<id>/submit" -H "Authorization: Bearer <your-key>" \
-H "Content-Type: application/json" -d '{ "signature_name": "Joe Smith" }'Account servicing
Submit post-boarding changes on a live merchant. All three use changes:write and follow the same rule: you submit, EPI reviews and commits (maker-checker). The merchant receives an anti-vishing verification code on bank/masterfile submissions.
Bank changes are dual control. One EPI BAC queue person verifies the request and a second, different BAC queue person commits it; the commit is what sends the new account to the processor. The response status is the real one: SUBMITTED, orAWAITING_MERCHANT_CONFIRM for a request the merchant must confirm first. A verified request reads APPROVED until it is committed.
| Field | Type | Description |
|---|---|---|
bank_namerequired | string | New bank name. |
routingrequired | string | 9-digit ABA routing. |
accountrequired | string | New deposit account number (4–17 digits). |
account_typeoptional | string | CHECKING (default) | SAVINGS. |
account_holder_nameoptional | string | Name on the account (≤22 chars, NACHA). |
voided_checkoptional | object | Optional supporting doc: { filename, content_base64, mime_type }. |
notesoptional | string | Free-text note for the reviewer. |
| Field | Type | Description |
|---|---|---|
dba_nameoptional | string | New DBA name. |
contact_nameoptional | string | New merchant contact name. |
emailoptional | string | New merchant email. |
phone / legal_phoneoptional | string | New DBA / legal phone. |
addressoptional | object | DBA address: { line1, city, state, zip }. |
legal_addressoptional | object | Legal address: { line1, city, state, zip }. |
descriptionoptional | string | Free-text for an "other" change the reviewer applies by hand. |
At least one masterfile field is required.
| Field | Type | Description |
|---|---|---|
new_valuesrequired | object | Sparse map of pricing field key → string value. Unknown keys are ignored. Pure decreases skip merchant approval; any increase routes to the merchant. One in-flight change per merchant. |
effective_start_dateoptional | string | Optional YYYY-MM-DD. |
effective_end_dateoptional | string | Optional YYYY-MM-DD. |
notesoptional | string | Free-text note. |
GET /change-requests (+ /{id}, changes:read) tracks any request through review to commit. The response includes the pricing Old→New diff; bank and masterfile fields are omitted.
# bank (deposit account) change – GIACT runs on submit
curl -X POST "https://www.cygma.cloud/api/v1/merchants/<id>/bank-change" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "bank_name": "Chase", "routing": "021000021", "account": "123456789", "account_type": "CHECKING" }'
# masterfile (AMF) change – any subset of fields
curl -X POST "https://www.cygma.cloud/api/v1/merchants/<id>/masterfile-change" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "email": "owner@merchant.com", "phone": "512-555-0100" }'
# pricing change – sparse map of field -> value
curl -X POST "https://www.cygma.cloud/api/v1/merchants/<id>/pricing-change" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "new_values": { "monthly_fee": "9.95", "amex_qual_rate": "2.89" } }'
# track it
curl "https://www.cygma.cloud/api/v1/change-requests?merchant_id=<id>" -H "Authorization: Bearer <your-key>"List applications
A paginated, read-only list of boarding applications. Requires the applications:read scope.
Query parameters:
page– 1-based page number (default 1).page_size– items per page, max 100 (default 25).status– filter by application status.merchant_id– filter to one merchant.
The response envelope (data, page, page_size, total, has_more) is the same across every list endpoint.
curl "https://www.cygma.cloud/api/v1/applications?page=1&page_size=25&status=SUBMITTED" \ -H "Authorization: Bearer <your-key>"
Statements (Cygma)
Pull a merchant’s Cygma processing statement. Requires the statements:read scope; the merchant must be in the key’s book of business. The list returns available periods (YYYYMM) with a ready-to-fetch pdf_url; the PDF endpoint streams the same branded statement the dashboard renders. 404 when no statement exists for that period.
# list the periods a merchant has a statement for curl "https://www.cygma.cloud/api/v1/merchants/<merchant-id>/statements" \ -H "Authorization: Bearer <your-key>" # download one period's PDF curl "https://www.cygma.cloud/api/v1/merchants/<merchant-id>/statements/202607/pdf" \ -H "Authorization: Bearer <your-key>" -o statement.pdf
Equipment catalog
The active device / SKU catalog that backs order line items. Requires orders:read. Filter with q (manufacturer / model / name) and category. This is reference data – not merchant-scoped.
curl "https://www.cygma.cloud/api/v1/catalog/equipment?q=clover&category=SMART_POS" \ -H "Authorization: Bearer <your-key>"
Pricing templates
The rate cards this key’s office can attach to a boarding application – the office’s own templates, its division’s, and the shared EPI library. Requires applications:read. Each row’s scope is office or epi_library. The same id is shown on every template in ISO360 (Pricing Templates) with a copy-to-clipboard API chip, so you can grab it from the dashboard instead of paging this endpoint.
curl "https://www.cygma.cloud/api/v1/pricing-templates" \ -H "Authorization: Bearer <your-key>"
Equipment templates
The saved equipment bundles this key’s office can attach to a boarding application (pass the id as equipment_template_id on POST/PATCH /applications). Attaching one materializes a draft order with the template’s lines. Requires applications:read; same office → division → EPI-library scope as pricing templates. The id is also shown on every template in ISO360 (Equipment Templates) with a copy-to-clipboard API chip.
curl "https://www.cygma.cloud/api/v1/equipment-templates" \ -H "Authorization: Bearer <your-key>"
Reference lists
The lookups partners need to set valid values on a boarding application. Requires reference:read. mcc is searchable + paginated; business-types is the fixed tax-classification list.
curl "https://www.cygma.cloud/api/v1/reference/mcc?q=restaurant" \ -H "Authorization: Bearer <your-key>" curl "https://www.cygma.cloud/api/v1/reference/business-types" \ -H "Authorization: Bearer <your-key>"
Webinar library
EPI’s webinar recordings, the same index agents see in ISO360 under Marketing. Requires webinars:read. The list is not merchant-scoped — it is the same catalog for every key.
GET /webinars is paginated and takes four filters, which combine: q (title, presenter, and description), category (a value from /webinars/categories), year (YYYY), and since (YYYY-MM-DD, inclusive). Results are newest first.
embed_url is a youtube-nocookie player URL you can drop straight into an iframe; thumbnail_url is the poster frame. Both are null when youtube_video_id is null. Recordings from 2016–2017 predate the YouTube channel and carry only bankcard_forum_url, a link to the Bankcard Forum thread; there is nothing to embed for those.
Only recordings EPI has published are returned. Videos are served by YouTube, not by this API — these endpoints return the index.
curl "https://www.cygma.cloud/api/v1/webinars?category=FEE_PROGRAMS&page_size=2" -H "Authorization: Bearer <your-key>" curl "https://www.cygma.cloud/api/v1/webinars?q=surcharge&year=2024" -H "Authorization: Bearer <your-key>" curl "https://www.cygma.cloud/api/v1/webinars?since=2026-01-01" -H "Authorization: Bearer <your-key>" curl "https://www.cygma.cloud/api/v1/webinars/playlists" -H "Authorization: Bearer <your-key>" curl "https://www.cygma.cloud/api/v1/webinars/categories" -H "Authorization: Bearer <your-key>"
Chargeback detail
One chargeback with its event timeline and evidence-document list – the detail behind a row from GET /chargebacks. Requires chargebacks:read; the merchant must be in the key’s book.
curl "https://www.cygma.cloud/api/v1/chargebacks/<chargeback-id>" \ -H "Authorization: Bearer <your-key>"
Residuals
Per-merchant residual line items for your user, office, and division allocations, by period. Requires residuals:read. Filter with period (YYYYMM) and mid.
curl "https://www.cygma.cloud/api/v1/residuals?period=202607" \ -H "Authorization: Bearer <your-key>"
Equipment orders
Price and place hardware orders. POST /pos-orders/quote (orders:read) computes the money exactly as the in-app order form does – office equipment markup, shipping derived from the SKUs + method, and the active sales-tax rate – without placing anything. POST /pos-orders (orders:write) places a Clover/Exatouch PURCHASE order as an agent using the same math. Look up SKUs from the catalog.
| Field | Type | Description |
|---|---|---|
brandrequired | string | CLOVER | EXATOUCH. |
merchant_idrequired | string | Merchant the order is for (must be in your book). |
line_itemsrequired | object[] | Each: { sku_id, qty }. Placement SKUs are rejected (they’re provisioned, not sold). |
ship_methodoptional | string | GROUND (default) | TWO_DAY | NEXT_DAY. |
ship_to_same_as_dbaoptional | boolean | Create only. Default true – otherwise supply the ship_* fields. |
ship_address / ship_city / ship_state / ship_zip / ship_phoneoptional | string | Create only, when not shipping to the DBA address. |
ship_stateoptional | string | Quote only – used for the sales-tax rate when different from the DBA state. |
notesoptional | string | Create only. Free-text. |
| Field | Type | Description |
|---|---|---|
merchant_idrequired | string | Merchant the TSF is for. |
account_typerequired | string | NEW_ACCOUNT | EXISTING_ADD_ORDER | SWAP_SEND_RCT. |
ship_to_typerequired | string | ISO | MERCHANT | OTHER | LOCAL_PICKUP | NO_SHIPPING. |
ship_methodrequired | string | GROUND | EXPRESS_2DAY | STD_OVERNIGHT | … |
bill_methodrequired | string | ACH_MERCHANT | CC_MERCHANT | CC_AGENT_ON_FILE | CC_AGENT_NEW | ACH_AGENT_ON_FILE. |
line_itemsrequired | object[] | Each: { equipment_id, frontend_id, quantity } (+ optional markup_amount, serial_number, programming). |
programoptional | string | PURCHASE (default) | EPI_FREE_TERMINAL. |
software / procharge / pos_gatewayoptional | object | Optional programming blocks (gateway type, frontend/backend, datawire mode, VAR-sheet email, etc.). |
submit_immediatelyoptional | boolean | Submit on create instead of leaving a draft. |
notesoptional | string | Free-text. |
Placing a terminal order requires the ORDER_POS permission on the key’s creator.
| Field | Type | Description |
|---|---|---|
merchant_idrequired | string | Merchant the order is for. |
case_sizerequired | string | HALF | FULL. |
shippingrequired | object | { name, address, city, state, zip, method } (+ optional suite, phone). |
paymentrequired | object | { method } – MAP | ACH | CC | INVOICE | AGENT_NET30 | AGENT_CARD (+ optional card_token). |
kindoptional | string | PAPER (default) | SUPPLIES | POS_PAPER. |
paper_typeoptional | string | Catalog paper code (for PAPER / POS_PAPER). |
item_kindoptional | string | For SUPPLIES: CHECK_PRESENTER | INK_RIBBON | OTHER. |
package_countoptional | integer | Boxes to ship (1–20). Default 1. |
notesoptional | string | Free-text. |
Order visibility (all orders:read, scoped to your book):
GET /pos-orders·GET /pos-orders/{id}– Clover / Exatouch orders.GET /terminal-orders·{id}– Terminal Setup Form / terminal orders (file-build → deployment).GET /supply-orders·{id}– supplies / paper orders (?kind=PAPER).
Placement SKUs can’t be purchased through the API (they’re provisioned, not sold). Placing terminal-setup and supply/paper orders via the API is a later addition – for now those two are read-only here.
# quote a Clover order (no order placed)
curl -X POST "https://www.cygma.cloud/api/v1/pos-orders/quote" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "brand": "CLOVER", "merchant_id": "<merchant-id>",
"line_items": [ { "sku_id": "<sku-id>", "qty": 1 } ], "ship_method": "GROUND" }'
# place it
curl -X POST "https://www.cygma.cloud/api/v1/pos-orders" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "brand": "CLOVER", "merchant_id": "<merchant-id>",
"line_items": [ { "sku_id": "<sku-id>", "qty": 1 } ] }'Terminal provisioning
Mint a merchant’s network identity and pull the paperwork. GET /merchants/{id}/terminals (terminals:read) lists devices and their V#/Cygma TID. Minting needs terminals:write.
| Field | Type | Description |
|---|---|---|
midoptional | string | Cygma-BIN MID (400321 / 411763 prefix). Falls back to the merchant's stored MID. |
mccoptional | string | 4-digit MCC. Falls back to the merchant's sic_code. Required to provision. |
Idempotent – a merchant that already has a primary TID returns 409 already_provisioned. A non-Cygma-BIN MID returns 422 unsupported_mid; a missing MCC returns 422 needs_mcc. Success also sends the M360 invite.
| Field | Type | Description |
|---|---|---|
labeloptional | string | Friendly device label. |
mccoptional | string | 4-digit MCC (defaults to the merchant's). |
serial_numberoptional | string | Device serial. |
linked_terminal_idoptional | string | Physical terminal id to attach to (null for standalone). |
POST /terminals/{id}/tsys-vnumber– mint/adopt the TSYS V# for a device. Idempotent. Optional body picks the build:capture_modeTERMINAL (Sierra, default) or HOST (Summit),build_classSTAGE / FULL withhwsw(MLTPASS, WEBPASS, HSTPYMT, TSEP, ICT250, ICT220, EQUINOX_AIO),debit,ebt_type+ebt_fns_number,tokenization+token_zone(DEFAULT / LIQUID PAYMENTS) +encryption_vendor(V Voltage / T TDES),auto_close_time(HHMM),commercial_card_capture,partial_auth(2 none / 3 all / 4 card present – sent on every brand range including Mastercard),accept_amex,accept_discover(Amex uses the ESA number on file or the OptBlue SE from the SIC; Discover the number assigned to the MID),merchant_email(MultiPass welcome email),partner_url(TSEP partner website). The service level (RNO / RAO / TAO) is derived and returned withcapture_modeand, for Summit,hc_pos_id.POST /terminals/{id}/tsys-maintain– one maintenance operation on a live V#, record read back and re-sent with only that change:opblock / unblock / merchant_info / hwsw (+hwsw) / unstage (Stage only → full build, +hwsw) / brand (+brandAMEX | DISC,enabled) / host_capture (Sierra → Summit, +auto_close_time,commercial_card_capture).POST /merchants/{id}/tsys-brands– enable Amex / Discover on the merchant's TSYS front-end record from the numbers on file. Idempotent; optional{ amex, discover }.GET /terminals/{id}/var-sheet– the branded TSYS VAR sheet as a PDF, regenerated fresh.
# provision the merchant's primary Cygma TID from its MID + MCC
curl -X POST "https://www.cygma.cloud/api/v1/merchants/<id>/cygma-tid" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "mid": "400321000001234", "mcc": "5812" }'
# mint a TSYS V# for a device, and pull its VAR sheet
curl -X POST "https://www.cygma.cloud/api/v1/terminals/<terminal-id>/tsys-vnumber" -H "Authorization: Bearer <your-key>"
# a Summit (host capture) V# with a 23:00 auto close
curl -X POST "https://www.cygma.cloud/api/v1/terminals/<terminal-id>/tsys-vnumber" -H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" -d '{"capture_mode":"HOST","auto_close_time":"2300","debit":true}'
curl "https://www.cygma.cloud/api/v1/terminals/<terminal-id>/var-sheet" -H "Authorization: Bearer <your-key>" -o var-sheet.pdfTerminal templates and settings
Build Merchant360 terminals from a settings template and manage their TMS settings. Settings come in layers, each storing only what it changed: default, linked template, merchant, terminal. A template edit reaches every linked merchant except the settings that merchant or one of its terminals changed. Reads need terminals:read; changes need terminals:write.
GET /terminal-templates·{id}– the EPI library (Restaurant, Retail, Lite POS, Fuel / Petrol, Services) plus your offices’ templates:id, name, industry, description, library, settings, updated_at.GET /merchants/{id}/terminal-settings–template, effectivesettings,sources(template / merchant / default / derived / handpoint),merchant_only_keys(same on every terminal) anddevice_keys(can differ per terminal). PINs read"set"or null.PATCH /merchants/{id}/terminal-settings–template_id(library or the merchant’s office; null unlinks),replace(template applies in full),settings(key to value; null clears). Handpoint gateway flags and TSYS batch close follow; seenotes.GET /merchants/{id}/devices– serial, model, nickname, location, is_default, provision_state, activated, last_seen_at, tid_present. Removed devices are left out;?include_removed=1lists them too, withremoved_atandremoved_reason.GET·PATCH /merchants/{id}/devices/{serial}/settings– one terminal’s settings and the values it changed (device_keysonly; others return422 not_device_keys).
| Field | Type | Description |
|---|---|---|
serialoptional | string | As printed on the device label. Required. |
modeloptional | string | PAXA920MAX, PAXA920PRO, PAXA920, PAXA77, PAXA800, PAXA35, PAXA80, PAXA960, PAXA8700 and every other Handpoint type (GET /openapi.json lists them). Optional: the first three digits of the serial name the model (274 A920 Max, 185 A920 Pro, 229 A35, 124 A80, and so on). Send it for the A50S (no prefix: without a model the call returns 422 model_required). A model the serial is not, or a numeric serial with no known prefix, returns 422 serial_model_mismatch. |
template_idoptional | string | Linked first when the merchant has no template yet. |
The V#, TID and API keys are never returned. activation.code signs the Payment App in on the device and is returned once, only when the request issued it (it is also emailed to the merchant). A stopped setup returns 422 with ok: false; send the same serial again to continue. 409 busy while another setup for the merchant is running.
# templates you can use (EPI library + your offices)
curl "https://www.cygma.cloud/api/v1/terminal-templates" -H "Authorization: Bearer <your-key>"
# build a Merchant360 terminal from a template (model from the serial prefix)
curl -X POST "https://www.cygma.cloud/api/v1/merchants/<id>/devices" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "serial": "2740010596", "template_id": "<template-id>" }'
# change merchant settings; null = back to the template / default
curl -X PATCH "https://www.cygma.cloud/api/v1/merchants/<id>/terminal-settings" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "settings": { "supervisorPin": "4831", "batchAutoCloseTime": "11:00 PM" } }'
# one terminal's own values (device_keys only)
curl -X PATCH "https://www.cygma.cloud/api/v1/merchants/<id>/devices/2740010596/settings" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "settings": { "terminalMode": "Integrated" } }'Terminal lifecycle
Fix a terminal set up as the wrong model, take a terminal off a merchant, or move it to another merchant. Each call needs terminals:write; a move needs access to both merchants. Responses carry ok, message, steps (each step and how it went) and device. The V#, TID and API keys are never returned. A removed device is not found by these calls (404).
POST /merchants/{id}/devices/{serial}/change-model:model(one of the seven types). Handpoint has no model change, so the device is released under the old model and set up again under the new one, on the same V#, HC POS ID and app login.POST /merchants/{id}/devices/{serial}/remove:modestock(back in EPI’s Handpoint pool, inventory RETURNED) orout_of_epi(also deleted from EPI’s pool, inventory RETIRED: RMA, sold, lost), optionalreason,force. The Payment App is signed out, the V# closed at TSYS and the device kept as history (provision_stateREMOVED). When it was the default terminal, the default moves to another working terminal.POST /merchants/{id}/devices/{serial}/move:to_merchant_id, optionalmodel,template_id(linked on the new merchant when it has none),force. A V# belongs to the merchant’s TSYS merchant number, so the device is removed here and set up on the new merchant with its own new V#.activation.codeis returned once, only when the request issued it, and is also emailed to the new merchant.
Sales on the device not yet in a batch settle on the merchant’s V#: remove and move return 409 unbatched_sales with the count. Close the batch first, or send force: true. A move whose setup on the new merchant stops after the device was removed here returns 422 with ok: false; send POST /merchants/{to_merchant_id}/devices with the serial to finish.
# set up as the wrong model: same V# and app login, new model at Handpoint
curl -X POST "https://www.cygma.cloud/api/v1/merchants/<id>/devices/2740010596/change-model" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "model": "PAXA920MAX" }'
# merchant returned the device: back to EPI stock for the next merchant
curl -X POST "https://www.cygma.cloud/api/v1/merchants/<id>/devices/2740010596/remove" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "mode": "stock", "reason": "MERCHANT CLOSED" }'
# move to another merchant in your book (new V#, new activation code)
curl -X POST "https://www.cygma.cloud/api/v1/merchants/<id>/devices/2740010596/move" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "to_merchant_id": "<other-merchant-id>" }'Webhooks
Get pushed the moment something happens instead of polling. Register an endpoint (webhooks:manage) and it receives events for the merchants in your book only. The secret is returned once; verify each delivery with HMAC-SHA256(sha256(secret), rawBody) against X-Webhook-Signature. Failed deliveries retry with backoff (1m → 5m → 30m → 2h → 12h, then give up).
| Field | Type | Description |
|---|---|---|
urlrequired | string | Your https:// endpoint. Non-https is rejected. |
eventsoptional | string[] | Which events to receive (see the list below), or omit for "*" (all). |
nameoptional | string | A label for your reference. |
Events: merchant.boarded, merchant.mid_assigned (the processor MID lands at approval - the payload carries the assigned mid), application.submitted, application.approved, application.declined, change_request.updated (a bank / masterfile / pricing change was confirmed, declined, or committed - includes auto_committed when a Plaid-verified bank change processed without manual review), pos_order.created, pos_order.shipped, chargeback.received, invoice.paid, merchant.churn_high, cancellation.requested, merchant.closed, and your office's own tickets: ticket.created, ticket.step_started, ticket.resolved (ticket_id, office_id, merchant, subject, summary, status, priority, due_at, owner and the step) – or "*"for all. Offices can also add endpoints without code from My Office > ISO360 API access > Webhooks.
GET /webhooks·GET /webhooks/{id}– list / inspect (with recent deliveries).DELETE /webhooks/{id}– remove an endpoint.POST /webhooks/{id}/test– send a syntheticwebhook.testto check your handler.
curl -X POST "https://www.cygma.cloud/api/v1/webhooks" \
-H "Authorization: Bearer <your-key>" -H "Content-Type: application/json" \
-d '{ "url": "https://yourapp.com/hooks/epi",
"events": ["merchant.boarded", "application.approved", "chargeback.received"] }'Routing-number lookup
Resolve a 9-digit ABA routing number to its financial institution. Data comes from the Federal Reserve FedACH Participants Directory, refreshed monthly. Requires the banking:read scope.
Path parameter:
routing– the 9-digit ABA routing number (non-digits are stripped).
Responses: 422 if the number isn’t 9 digits or fails the ABA Mod-10 checksum; 404 if it passes the checksum but isn’t in the directory (a newer/smaller institution).
GET /api/v1/banking/routing/121000248
This is a fixed sample. The live GET /api/v1/banking/routing/{routing} looks up any routing number – add Authorization: Bearer <your-key> and call it from your server.
curl "https://www.cygma.cloud/api/v1/banking/routing/121000248" \ -H "Authorization: Bearer <your-key>"
Card BIN lookup
Identify a card from its leading digits. Alongside brand, credit / debit / prepaid, issuing bank, country, and the surcharge verdict, the response decodes the full card profile: the card indicator (e.g. H → “Debit hybrid — PIN and signature”), PIN / signature acceptance, the Durbin regulated-issuer flag, and the named debit networks the card routes on (with PINless capability). A credit card that also carries debit networks is flagged dual_routing. The product_id is decoded to a human-readable product tier in product_id_label (e.g. D → “Visa Signature Preferred”). Requires the banking:read scope.
Test data only. This endpoint serves a small static sample set — the well-known test BINs (411111, 555555, 371449, 601100, a debit, a prepaid, and a regulated example) — so you can build and exercise every response shape. It does not serve EPI’s production BIN directory; production lookups are available by agreement with Electronic Payments.
Path parameter:
bin— the first 6–16 digits of the card number (non-digits are stripped).
Responses: 200 with the card attributes; 422 if the BIN isn’t 6–16 digits; 404 for any BIN outside the sample set (the body lists the available samples).
GET /api/v1/banking/bin/411111
These are fixed test BINs. The live endpoint serves the same sample set only; production BIN lookups are available by agreement with Electronic Payments.
curl "https://www.cygma.cloud/api/v1/banking/bin/486208" \ -H "Authorization: Bearer <your-key>"
Responses
Successful responses return JSON with the resource under a data key. List endpoints add page, page_size, total, and has_more for pagination. The status code tells you what happened:
| Status | Meaning |
|---|---|
| 200 | OK — a read, lookup, or list succeeded; the resource is under data. |
| 201 | Created — a new resource (e.g. a proposal from an analyzed statement) was created. |
| 202 | Accepted — the request was queued for asynchronous processing. |
// Reads + single-object responses wrap the object in `data`.
{ "data": { "id": "...", "...": "..." } }
// List endpoints wrap an array in `data` with pagination.
{ "data": [ { "...": "..." } ], "page": 1, "page_size": 25, "total": 42, "has_more": true }Errors
| Status | Meaning |
|---|---|
| 401 | Missing, malformed, invalid, or revoked key. |
| 403 | Key lacks the required scope (or isn’t office-bound). |
| 413 | Statement file too large (25 MB max). |
| 415 | Body isn’t multipart, or the file isn’t a PDF. |
| 422 | Missing a required field, or the statement couldn’t be parsed. |
| 429 | Rate limit exceeded – back off and retry. |
// Errors return a JSON body with a single `error` string.
{ "error": "Missing required scope: proposals:create" }Versioning
The API is versioned in the path (/api/v1). Within a version we only make backward-compatible changes: new fields are always optional, and we never remove or repurpose an existing field. A breaking change ships under a new version, and the old one keeps running through a deprecation window.
Build a tolerant client: ignore response fields you don’t recognize, and don’t depend on field order.