Agent platform · REST

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.

Overview

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.
New here? Get a free read-only key.

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 sandbox
Bearer key, per office

Authentication

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 returns 429.
  • 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.

↑ Request – what you send
# Every request carries your office key as a bearer token.
curl https://www.cygma.cloud/api/v1/applications \
  -H "Authorization: Bearer epi_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Self-serve read-only key

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, and webinars: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 403 on those.
  • Capped. Five active keys per email address, and a global daily ceiling. Lost a key? Request another.
↑ Request – what you send
# 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>"
2 steps

Quickstart

  1. Create your office key in My Office → ISO360 API.
  2. 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.
↑ Request – what you send
# 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"
POST /api/v1/proposals/analyze-statement

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:

FieldRequiredNotes
statementyesPDF file, 25 MB max.
merchant_nameyesBusiness name.
mccno4-digit MCC. Inferred when omitted.
address, city, state, zipnoPhysical address. Blanks are backfilled from the statement.
dba_name, legal_name, phone, emailnoContact 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).

↑ Request – what you send
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"
POST /api/v1/proposals

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_id or lead_id).
  • GET /proposals · GET /proposals/{id} – list / fetch.
  • POST /proposals/{id}/versions – save a priced version (knobs.pricing_model required).
  • POST /proposals/{id}/pdf – generate the merchant PDF; GET the same path downloads it.
  • POST /proposals/{id}/send – email it (to_email) and mark SENT.
  • POST /proposals/{id}/accept – mark ACCEPTED.
POST /proposals – open a draft
FieldTypeDescription
merchant_idoptionalstringAnchor to an existing merchant. Provide this OR lead_id (exactly one).
lead_idoptionalstringAnchor to a lead instead of a merchant.
target_savings_pctoptionalnumberOptional savings target to prompt the pricing engine.
target_savings_dollarsoptionalnumberOptional dollar savings target.
POST /proposals/{id}/versions – price it
FieldTypeDescription
knobsrequiredobjectPricing 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)optionalnumberOptional. 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_processingoptionalobjectOptional 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).
modeoptionalstringcreate (default, new version) | update_latest (overwrite the latest).
POST /proposals/{id}/send
FieldTypeDescription
to_emailrequiredstringRecipient for the merchant-facing PDF. Marks the proposal SENT.

The response and the PDF carry the merchant-facing pricing figures.

↑ Request – what you send
# 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" }'
POST /api/v1/applications

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.

POST /applications – top level
FieldTypeDescription
businessrequiredobjectBusiness identity block (see below).
preferred_backendoptionalstringProcessor to board on: NORTH (default), OMAHA, or CYGMA.
ownersoptionalobject[]Beneficial owners / signers. (≥1 required to submit.)
bankoptionalobjectDeposit account. (Required to submit.)
riskoptionalobjectProcessing volumes + sales mix (feeds underwriting).
pricing_template_idoptionalstringAttach 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_idoptionalstringAttach 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.
business
FieldTypeDescription
dba_namerequiredstringDoing-business-as name.
legal_namerequiredstringLegal entity name (W-9 line 1).
addressrequiredstringPhysical / DBA street address.
cityrequiredstringDBA city.
staterequiredstring2-letter state.
ziprequiredstringDBA ZIP.
business_start_dateoptionalstringYYYY-MM-DD. (Required to submit.)
mccoptionalstring4-digit merchant category code. (Required to provision a Cygma TID.) Look one up via /reference/mcc.
einoptionalstringFederal Tax ID / EIN. Encrypted at rest; only last-4 is ever returned.
tin_typeoptionalstringSSN | EIN | ITIN.
tax_classificationoptionalstringEntity type – see /reference/business-types.
business_typeoptionalstringFree-text business description.
phoneoptionalstringDBA phone.
emailoptionalstringMerchant contact email (lowercased).
websiteoptionalstringMerchant website.
owners[] – each owner
FieldTypeDescription
full_namerequiredstringOwner's full legal name.
ownership_pctoptionalnumberPercent ownership. Owners ≥25% must have SSN, DOB, and full address to submit.
ssnoptionalstring9-digit SSN. Encrypted at rest. (Required to submit for owners ≥25% + control persons.)
doboptionalstringYYYY-MM-DD. Encrypted at rest. (Required to submit, same rule.)
address / city / state / zipoptionalstringOwner home address. (Required to submit, same rule.)
is_signeroptionalbooleanMarks a signing owner. Signers must total ≥50% equity to submit (auto-promoted if short).
is_control_onlyoptionalbooleanFinCEN control person with no equity – still needs SSN/DOB/address.
titleoptionalstringOwner title (e.g. President).
email / phoneoptionalstringOwner contact.
bank (primary deposit account)
FieldTypeDescription
routingoptionalstring9-digit ABA routing. (Required to submit.) Validate via /banking/routing.
accountoptionalstringDeposit account number. (Required to submit.)
account_typeoptionalstringCHECKING (default) | SAVINGS.
bank_nameoptionalstringBank name (recommended).
risk (all optional – feeds underwriting)
FieldTypeDescription
annual_volumeoptionalnumberAnnual card volume, dollars.
average_ticketoptionalnumberAverage sale, dollars.
high_ticketoptionalnumberHighest single sale, dollars.
pct_swiped / pct_keyed / pct_moto / pct_internetoptionalnumberEntry mix %, should total 100.
pct_retail / pct_b2b / pct_mobileoptionalnumberSales-location mix %.
POST /applications/{id}/submit
FieldTypeDescription
signature_namerequiredstringThe signer's typed name (the e-signature).
signature_bloboptionalstringOptional signature image as a data URL.
confirmation_signature_nameoptionalstringDefaults to signature_name.
confirmation_signature_bloboptionalstringDefaults 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.

↑ Request – what you send
# 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" }'
POST /api/v1/merchants/{id}/{bank-change,pricing-change,masterfile-change}

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.

POST /merchants/{id}/bank-change
FieldTypeDescription
bank_namerequiredstringNew bank name.
routingrequiredstring9-digit ABA routing.
accountrequiredstringNew deposit account number (4–17 digits).
account_typeoptionalstringCHECKING (default) | SAVINGS.
account_holder_nameoptionalstringName on the account (≤22 chars, NACHA).
voided_checkoptionalobjectOptional supporting doc: { filename, content_base64, mime_type }.
notesoptionalstringFree-text note for the reviewer.
POST /merchants/{id}/masterfile-change (send only what changes)
FieldTypeDescription
dba_nameoptionalstringNew DBA name.
contact_nameoptionalstringNew merchant contact name.
emailoptionalstringNew merchant email.
phone / legal_phoneoptionalstringNew DBA / legal phone.
addressoptionalobjectDBA address: { line1, city, state, zip }.
legal_addressoptionalobjectLegal address: { line1, city, state, zip }.
descriptionoptionalstringFree-text for an "other" change the reviewer applies by hand.

At least one masterfile field is required.

POST /merchants/{id}/pricing-change
FieldTypeDescription
new_valuesrequiredobjectSparse 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_dateoptionalstringOptional YYYY-MM-DD.
effective_end_dateoptionalstringOptional YYYY-MM-DD.
notesoptionalstringFree-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.

↑ Request – what you send
# 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>"
GET /api/v1/applications

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.

↑ Request – what you send
curl "https://www.cygma.cloud/api/v1/applications?page=1&page_size=25&status=SUBMITTED" \
  -H "Authorization: Bearer <your-key>"
GET /api/v1/merchants/{id}/statements

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.

↑ Request – what you send
# 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
GET /api/v1/catalog/equipment

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.

↑ Request – what you send
curl "https://www.cygma.cloud/api/v1/catalog/equipment?q=clover&category=SMART_POS" \
  -H "Authorization: Bearer <your-key>"
GET /api/v1/pricing-templates

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.

↑ Request – what you send
curl "https://www.cygma.cloud/api/v1/pricing-templates" \
  -H "Authorization: Bearer <your-key>"
GET /api/v1/equipment-templates

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.

↑ Request – what you send
curl "https://www.cygma.cloud/api/v1/equipment-templates" \
  -H "Authorization: Bearer <your-key>"
GET /api/v1/reference/{mcc|business-types}

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.

↑ Request – what you send
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>"
GET /api/v1/webinars · /webinars/playlists · /webinars/categories

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.

↑ Request – what you send
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>"
GET /api/v1/chargebacks/{id}

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.

↑ Request – what you send
curl "https://www.cygma.cloud/api/v1/chargebacks/<chargeback-id>" \
  -H "Authorization: Bearer <your-key>"
GET /api/v1/residuals

Residuals

Per-merchant residual line items for your user, office, and division allocations, by period. Requires residuals:read. Filter with period (YYYYMM) and mid.

↑ Request – what you send
curl "https://www.cygma.cloud/api/v1/residuals?period=202607" \
  -H "Authorization: Bearer <your-key>"
POST /api/v1/pos-orders

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.

POS – /pos-orders/quote and /pos-orders (create)
FieldTypeDescription
brandrequiredstringCLOVER | EXATOUCH.
merchant_idrequiredstringMerchant the order is for (must be in your book).
line_itemsrequiredobject[]Each: { sku_id, qty }. Placement SKUs are rejected (they’re provisioned, not sold).
ship_methodoptionalstringGROUND (default) | TWO_DAY | NEXT_DAY.
ship_to_same_as_dbaoptionalbooleanCreate only. Default true – otherwise supply the ship_* fields.
ship_address / ship_city / ship_state / ship_zip / ship_phoneoptionalstringCreate only, when not shipping to the DBA address.
ship_stateoptionalstringQuote only – used for the sales-tax rate when different from the DBA state.
notesoptionalstringCreate only. Free-text.
POST /terminal-orders (Terminal Setup Form)
FieldTypeDescription
merchant_idrequiredstringMerchant the TSF is for.
account_typerequiredstringNEW_ACCOUNT | EXISTING_ADD_ORDER | SWAP_SEND_RCT.
ship_to_typerequiredstringISO | MERCHANT | OTHER | LOCAL_PICKUP | NO_SHIPPING.
ship_methodrequiredstringGROUND | EXPRESS_2DAY | STD_OVERNIGHT | …
bill_methodrequiredstringACH_MERCHANT | CC_MERCHANT | CC_AGENT_ON_FILE | CC_AGENT_NEW | ACH_AGENT_ON_FILE.
line_itemsrequiredobject[]Each: { equipment_id, frontend_id, quantity } (+ optional markup_amount, serial_number, programming).
programoptionalstringPURCHASE (default) | EPI_FREE_TERMINAL.
software / procharge / pos_gatewayoptionalobjectOptional programming blocks (gateway type, frontend/backend, datawire mode, VAR-sheet email, etc.).
submit_immediatelyoptionalbooleanSubmit on create instead of leaving a draft.
notesoptionalstringFree-text.

Placing a terminal order requires the ORDER_POS permission on the key’s creator.

POST /supply-orders (paper / supplies)
FieldTypeDescription
merchant_idrequiredstringMerchant the order is for.
case_sizerequiredstringHALF | FULL.
shippingrequiredobject{ name, address, city, state, zip, method } (+ optional suite, phone).
paymentrequiredobject{ method } – MAP | ACH | CC | INVOICE | AGENT_NET30 | AGENT_CARD (+ optional card_token).
kindoptionalstringPAPER (default) | SUPPLIES | POS_PAPER.
paper_typeoptionalstringCatalog paper code (for PAPER / POS_PAPER).
item_kindoptionalstringFor SUPPLIES: CHECK_PRESENTER | INK_RIBBON | OTHER.
package_countoptionalintegerBoxes to ship (1–20). Default 1.
notesoptionalstringFree-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.

↑ Request – what you send
# 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 } ] }'
POST /api/v1/merchants/{id}/cygma-tid

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.

POST /merchants/{id}/cygma-tid – primary TID (standalone board)
FieldTypeDescription
midoptionalstringCygma-BIN MID (400321 / 411763 prefix). Falls back to the merchant's stored MID.
mccoptionalstring4-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.

POST /merchants/{id}/cygma-device-tid – additional device TID
FieldTypeDescription
labeloptionalstringFriendly device label.
mccoptionalstring4-digit MCC (defaults to the merchant's).
serial_numberoptionalstringDevice serial.
linked_terminal_idoptionalstringPhysical 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_mode TERMINAL (Sierra, default) or HOST (Summit), build_class STAGE / FULL with hwsw (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 with capture_mode and, 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: op block / unblock / merchant_info / hwsw (+ hwsw) / unstage (Stage only → full build, + hwsw) / brand (+ brand AMEX | 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.
↑ Request – what you send
# 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.pdf
POST /api/v1/merchants/{id}/devices

Terminal 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, effective settings, sources (template / merchant / default / derived / handpoint), merchant_only_keys (same on every terminal) and device_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; see notes.
  • 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=1 lists them too, with removed_at and removed_reason.
  • GET · PATCH /merchants/{id}/devices/{serial}/settings – one terminal’s settings and the values it changed (device_keys only; others return 422 not_device_keys).
POST /merchants/{id}/devices – build a terminal
FieldTypeDescription
serialoptionalstringAs printed on the device label. Required.
modeloptionalstringPAXA920MAX, 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_idoptionalstringLinked 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.

↑ Request – what you send
# 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" } }'
POST /api/v1/merchants/{id}/devices/{serial}/move

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: mode stock (back in EPI’s Handpoint pool, inventory RETURNED) or out_of_epi (also deleted from EPI’s pool, inventory RETIRED: RMA, sold, lost), optional reason, force. The Payment App is signed out, the V# closed at TSYS and the device kept as history (provision_state REMOVED). When it was the default terminal, the default moves to another working terminal.
  • POST /merchants/{id}/devices/{serial}/move: to_merchant_id, optional model, 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.code is 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.

↑ Request – what you send
# 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>" }'
POST /api/v1/webhooks

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

POST /webhooks
FieldTypeDescription
urlrequiredstringYour https:// endpoint. Non-https is rejected.
eventsoptionalstring[]Which events to receive (see the list below), or omit for "*" (all).
nameoptionalstringA 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 synthetic webhook.test to check your handler.
↑ Request – what you send
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"] }'
GET /api/v1/banking/routing/{routing}

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

Try itsimulated example – no live call

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.

↑ Request – what you send
curl "https://www.cygma.cloud/api/v1/banking/routing/121000248" \
  -H "Authorization: Bearer <your-key>"
GET /api/v1/banking/bin/{bin} – test data

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

Try itsimulated example – no live call, test data only

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.

↑ Request – what you send
curl "https://www.cygma.cloud/api/v1/banking/bin/486208" \
  -H "Authorization: Bearer <your-key>"
Success status codes

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:

StatusMeaning
200OK — a read, lookup, or list succeeded; the resource is under data.
201Created — a new resource (e.g. a proposal from an analyzed statement) was created.
202Accepted — the request was queued for asynchronous processing.
↑ Request – what you send
// 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 }
HTTP status + { error }

Errors

StatusMeaning
401Missing, malformed, invalid, or revoked key.
403Key lacks the required scope (or isn’t office-bound).
413Statement file too large (25 MB max).
415Body isn’t multipart, or the file isn’t a PDF.
422Missing a required field, or the statement couldn’t be parsed.
429Rate limit exceeded – back off and retry.
↑ Request – what you send
// Errors return a JSON body with a single `error` string.
{ "error": "Missing required scope: proposals:create" }
Stability

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.