---
name: sumit-api
description: Use when working with SUMIT (OfficeGuy) payment API — charging, tokenizing, refunding, webhooks, reconciliation, vendor onboarding, or any SUMIT API integration question.
---

# SUMIT API Reference

SUMIT (OfficeGuy) = Israeli marketplace clearing platform. API base: `https://api.sumit.co.il`. Swagger via `POST /swagger` or SUMIT dashboard.

---

## Credentials

| Secret | Source | Purpose |
|--------|--------|---------|
| `SUMIT_COMPANY_ID` | Dashboard → Company Settings | Required every API call (integer) |
| `SUMIT_API_KEY` | Dashboard → API Keys → `API_KEY` | Server-side private key — never expose to browser |
| `SUMIT_API_PUBLIC_KEY` | Dashboard → API Keys → `PUBLIC_KEY` | Browser-safe key for payments.js tokenization |
| `SUMIT_API_BASE` | `https://api.sumit.co.il` | API base URL |
| `SUMIT_PAYMENTS_JS_URL` | `https://app.sumit.co.il/scripts/payments.js` | Client-side payment form script |

**Two distinct keys:**
- `API_KEY` → server calls (`/billing/payments/charge/` etc.)
- `PUBLIC_KEY` → `OfficeGuy.Payments.BindFormSubmit()` in browser

**CompanyID is integer** — env vars strings at runtime; coerce: `Number(SUMIT_COMPANY_ID)`.

---

## Payment Flow (synchronous — no webhooks needed)

```
1. Browser: payments.js tokenizes card → single-use token
2. Server: POST /billing/payments/multivendorcharge/ { token, amount, items }
3. Response: success/failure returned synchronously in same HTTP call
4. Server: update DB based on response — no async event needed
```

---

## Key API Endpoints

### Charge (multi-vendor)
```
POST /billing/payments/multivendorcharge/
```
Splits payment across platform + vendor(s). Use this, not `/billing/payments/charge/`.

Request body:
```json
{
  "SingleUseToken": "<token from payments.js>",
  "Customer": { "Name": "...", "EmailAddress": "...", "Phone": "..." },
  "Items": [ <ChargeItem>, <ChargeItem> ],
  "ExternalIdentifier": "<your purchase ID>",
  "SendDocumentByEmail": true,
  "DocumentType": 1,
  "VATIncluded": true,
  "Payments_Count": 1,
  "AuthoriseOnly": true
}
```

Credentials in each `ChargeItem`, not top-level.

### ChargeItem structure
```typescript
{
  CompanyID: number,          // integer — NOT string
  APIKey: string,             // private API key for this company
  Item: { Name: string },     // line-item name on invoice
  UnitPrice: number,          // ILS decimal (e.g. 99.90) — NOT agorot
  Quantity: number
}
```

**No `Description` field on ChargeItem.** Use `Item.Name`.

### Fee split (two-item charge)
```typescript
platformAgorot = Math.floor(totalAgorot * PLATFORM_FEE_PCT / 100)
vendorAgorot   = totalAgorot - platformAgorot   // remainder avoids rounding drift
```
Convert agorot → ILS at call site: `vendorAgorot / 100`.

### Charge response
`Data.Vendors` array — order matches `Items` order:
```typescript
{
  CompanyID: number,
  Payment: { PaymentID, ExternalIdentifier, Amount, Status, CreatedAt },
  DocumentID: number,
  DocumentNumber: number,
  DocumentDownloadURL: string
}
```
Persist `PaymentID` + `DocumentID` for both vendor + platform — needed for refunds.

### Get Payment
```
POST /billing/payments/get/
Body: { Credentials, PaymentID }
```

### Tokenize Card (server-side)
```
POST /creditguy/vault/tokenizesingleuse/
Credentials: { CompanyID, APIPublicKey }  ← PUBLIC key
```

### AuthoriseOnly mode (J5 hold)
`"AuthoriseOnly": true` in `/billing/payments/charge/` body. Shva J5 pre-auth (תפיסת מסגרת) — reserves credit limit, no capture. Returns `AuthNumber` + `CardToken` (multi-use). Probe-confirmed on test terminal.

**Hold lifetime:** ~6-7 days Shva. Auto-expires — no release call needed. Deals >6d: place J5 in final 6-day window before capture.

**Card-class restriction:** J5 works on **credit cards only**. Israeli debit (כרטיסי חיוב מיידי) + prepaid → code `003` (issuer rejection) — NOT code bug. Reject at commit, ask for credit card.

---

## Hold-and-Capture Flow (J5 → J4)

Deferred-billing (group deals, escrow, deposits): authorize at commit, capture at deadline.

### Step 1 — J2 validity check (at commit)
```
POST /billing/payments/charge/
Body: { ..., AuthoriseOnly: true, Amount: 1 }   // or use ParamJ:2 if exposed
```
Cheapest validation. Returns `CardToken` (multi-use). Use for later J5/J4 calls. Reject commit on failure — user fixes card before deadline.

### Step 2 — J5 pre-auth (at `max(commit, deadline - 6 days)`)
```
POST /billing/payments/charge/
Body: {
  CreditCard_Token: <multi-use token from J2>,
  AuthoriseOnly: true,
  Amount: <full deal amount>,
  ...
}
Response: { AuthNumber: "0676164", CardToken: "..." }
```
Persist `AuthNumber` + `CardToken` — required for capture.

### Step 3 — Capture (at deadline) — TWO PATHS, both probe-confirmed:

**Path A (preferred): multivendorcharge with AuthNumber**
```
POST /billing/payments/multivendorcharge/
Body: {
  PaymentMethod: { CreditCard_Token: <token> },
  CreditCardAuthNumber: <AuthNumber from J5>,
  Items: [...],     // platform + vendor split as usual
  ...
}
```
Captures J5 + creates accounting docs + vendor split in one call. Use for production.

**Path B (raw J4): gateway transaction**
```
POST /creditguy/gateway/transaction/
Body: {
  ParamJ: 4,
  Amount: <agorot or ILS — verify your terminal config>,
  AuthNumber: <from J5>,
  ...
}
```
Lower-level. No accounting docs (call `/accounting/documents/create/` after). Use only if multivendorcharge unavailable.

### Step 4 — Receipt
```
POST /accounting/documents/create/
```
Best-effort, non-fatal. Handle failure separately from capture success.

### Idempotency
- Dedup capture by `AuthNumber` — never capture same AuthNumber twice
- Track `j5_attempt_count` for retry-with-cap pattern
- 60s rate-limit window on duplicate `multivendorcharge` (returns OG_20) — back off + retry

---

## Refund Flow

Two-leg negative charge + document cancellation:

**Leg 1 — Vendor refund:**
```
POST /billing/payments/charge/
CompanyID: vendor's, APIKey: vendor's
UnitPrice: -(vendorAmountIls)
ExternalIdentifier: "refund:vendor:{purchaseId}"
```

**Leg 2 — Platform fee refund:**
```
POST /billing/payments/charge/
CompanyID: platform's, APIKey: platform's
UnitPrice: -(platformAmountIls)
ExternalIdentifier: "refund:platform:{purchaseId}"
```

**Leg 3 — Cancel original documents (best-effort, non-fatal):**
```
POST /accounting/documents/cancel/
Body: { Credentials, DocumentID, Description }
```
Creates credit note — does NOT delete original. Call for both vendor + platform docs.

Partial failure: Leg 1 success + Leg 2 fail → manual intervention. Design error codes for this.

---

## Cancel Document
```
POST /accounting/documents/cancel/
Body: { Credentials, DocumentID, Description }
```
Creates cancellation doc (credit note). Does NOT delete original. Returns new `DocumentID`.

---

## Create Document
```
POST /accounting/documents/create/
Body: { Credentials, Details: { Type: <int>, Customer, IsDraft, ... } }
```
Type = integer (0=Invoice, 1=InvoiceAndReceipt, 2=Receipt, 3=ProformaInvoice). Pass number, not string.

---

## Vendor Onboarding (4-step state machine)

Vendors need own SUMIT company + terminal for split payments. Idempotent — safe to resume any step.

### Step 1 — Create Company
```
POST /website/companies/create/
Credentials: { CompanyID: <platform>, APIKey: <platform> }
Body: { BusinessName, ContactEmail, ContactPhone }
Returns: { CompanyID: <vendor>, APIKey: <vendor> }
```
Encrypt vendor `APIKey` at rest immediately. Never store plaintext.

### Step 2 — Install Applications
```
POST /website/companies/installapplications/
Credentials: { CompanyID: <vendor>, APIKey: <vendor> }
Body: { ApplicationName: "Billing" }
```
Call twice: `"Billing"` then `"Accounting"`.

### Step 3 — Open Terminal
```
POST /billing/generalbilling/openterminal/
Credentials: { CompanyID: <vendor>, APIKey: <vendor> }
Body: { BankCode, BranchCode, AccountNumber, ContactEmail, ContactPhone }
```
Bank codes = Israeli standard bank codes (integers).

### Step 4 — Verify Active
```
POST /billing/generalbilling/getterminalstatus/
Credentials: { CompanyID: <vendor>, APIKey: <vendor> }
```
Poll until `TerminalStatus === "Active"`. Activation: minutes to hours.

### State tracking (store in DB)
- `sumitCompanyId` (integer)
- `sumitApiKeyEnc` (encrypted)
- `sumitBankCode`, `sumitBranchCode`, `sumitAccountLast4`
- `sumitTerminalActive` (boolean)
- `onboardingState`: `'not_started' | 'company_created' | 'apps_installed' | 'terminal_active'`

---

## Vendor API Key Encryption at Rest

Use AES-256-GCM. Wire format (base64): `iv (12 bytes) | authTag (16 bytes) | ciphertext`

```typescript
async function encryptVendorKey(plaintext: string, keyB64: string): Promise<string> {
  const key = await crypto.subtle.importKey('raw', base64ToBytes(keyB64), 'AES-GCM', false, ['encrypt']);
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, key, new TextEncoder().encode(plaintext));
  const result = new Uint8Array(12 + encrypted.byteLength);
  result.set(iv, 0);
  result.set(new Uint8Array(encrypted), 12);
  return bytesToBase64(result);
}

async function decryptVendorKey(cipherB64: string, keyB64: string): Promise<string> {
  const data = base64ToBytes(cipherB64);
  const iv = data.slice(0, 12);
  const ciphertext = data.slice(12);   // includes 16-byte tag at end
  const key = await crypto.subtle.importKey('raw', base64ToBytes(keyB64), 'AES-GCM', false, ['decrypt']);
  const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 128 }, key, ciphertext);
  return new TextDecoder().decode(plain);
}
```

Decrypt only when making API calls. Never log plaintext keys.

---

## CRM Triggers

**Are:** CRM automation (like Zapier). Fire when cards (rows) in folders (tables) Created/Updated/Archived/Deleted. Payload = full CRM card. Register via `POST /triggers/triggers/subscribe/ { URL, Folder, TriggerType }`.

**Are NOT:** Payment webhooks. No `payment.created`/`payment.failed` events. Those event names are fictional.

**When useful:** Documents folder (`1812460183`) fires on accounting doc create/cancel. Payments folder (`1812472284`) fires on payment card changes.

**Trigger payload format:** Unknown shape — capture via inspection tool (RequestBin, ngrok). Subscribe → create test doc → capture.

---

## Reconciliation

No clean event data → poll:
```
POST /billing/payments/list/
Body: { Credentials, ... }
```
Run on cron. Also poll `/billing/payments/get/` for records stuck `PENDING` beyond timeout.

---

## Webhook Handler — NOT NEEDED

Charge + refund synchronous. CRM triggers send card data, not payment events. No webhook infrastructure needed.

---

## Known Folder IDs (CRM)

| ID | Hebrew | English |
|----|--------|---------|
| `1812460183` | מסמכים | Documents |
| `1812472284` | תשלומים במערכת | System Payments |
| `1812460129` | תשלומים כלליים | General Payments |
| `1812460580` | קבלות | Receipts |
| `1812460484` | חשבונות/קבלות | Accounts/Receipts |
| `1812460732` | חשבוניות | Invoices |
| `1831005808` | פעולות קריאות HTTP | HTTP Request Actions |

Full list: `POST /crm/schema/listfolders/` with credentials.

---

## Error Codes

### SUMIT application codes
| Code | Meaning |
|------|---------|
| `1001` | Card declined |
| `1002` | Insufficient funds |
| `1003` | Token expired |
| `1004` | Invalid token |
| `1005` | Duplicate payment |
| `2001` | Terminal inactive — onboarding incomplete |
| `2002` | Auth failed — wrong credentials |
| `4290` | Rate limited |
| `5000` | SUMIT server error |
| `OG_20` | Duplicate-charge guard — 60s window. Back off + retry. |
| `OG_07` | Amount=0 — likely wrong field name. Use `Amount`, NOT `TransactionAmount` on `/creditguy/gateway/transaction/`. |

### Shva clearing response codes (in Shva responses underneath SUMIT)
| Code | Meaning |
|------|---------|
| `000` | Approved |
| `003` | Issuer decline — card-level rejection. Often debit/prepaid card attempting J5 hold (not supported by issuer). NOT terminal config issue. |
| `004` | Issuer decline — generic. Card or limit issue. NOT terminal config. |
| `044` | Terminal config — missing permissions for requested operation |
| `349` | Terminal config — system error / not authorized for flow |

**Disambiguating 003/004 vs terminal config (044/349):** mixed results across test cards → card-class issue (003/004). All cards fail same code → terminal config (044/349) — open SUMIT support ticket.

---

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| String for DocumentType | Integer: `3` not `"ProformaInvoice (3)"` |
| Expecting `payment.created` webhooks | Payments sync — no webhooks |
| `SUMIT_API_PUBLIC_KEY` for server calls | Server calls use `SUMIT_API_KEY` (private) |
| `SUMIT_API_KEY` for tokenization | Tokenization uses `SUMIT_API_PUBLIC_KEY` (public) |
| Treating cancel as delete | Cancel creates credit note, original stays |
| `/billing/payments/charge/` for multi-vendor | Use `/billing/payments/multivendorcharge/` |
| `CompanyID` as string | Coerce: `Number(SUMIT_COMPANY_ID)` |
| `ChargeItem.Description` | No such field — use `Item: { Name: string }` |
| Vendor APIKey plaintext in DB | Encrypt at rest (AES-256-GCM) |
| Both amounts calculated from percentage | Platform = floor(total * pct / 100), vendor = total - platform |
| `UnitPrice` in agorot | ILS decimal: `99.90`, not `9990` |
| `TransactionAmount` on gateway endpoint | Use `Amount`. Wrong field → OG_07 "amount=0" error |
| Code 003/004 = "terminal not configured" | NO. Issuer rejections (often debit-card/J5 mismatch). Probe multiple cards before escalating. |
| Re-using same AuthNumber across captures | AuthNumber single-use. Track `j5_attempt_count` + dedup. |
| Tokenize server-side via `/creditguy/vault/tokenize/` | Unreliable. Use J2 (`AuthoriseOnly:true` w/ small amount) → `CardToken` returned is multi-use. |
| Hold release call needed | No release endpoint. Shva J5 auto-expires ~6-7 days. Just don't capture. |

---

## Reality vs Common False Assumptions

> Confirmed in practice. LLM training data unreliable on SUMIT specifics.

| LLM often assumes | Reality |
|---|---|
| Sandbox / separate test API exists | One API only: `https://api.sumit.co.il`. Testing = test terminal (מסוף סליקה), same URL, different terminal config. |
| Webhooks for `payment.created` / `payment.failed` | No payment webhooks. Charge result synchronous in HTTP response. |
| CRM Triggers = payment webhooks | Different. Triggers fire on CRM card changes. Payload = full CRM card, not payment event. Not needed for charge/refund. |
| Feature flag needed to gate refunds "until sandbox confirmed" | No sandbox, no gate. Refunds work live. Handle idempotency at DB layer. |
| `ChargeItem` has `Description` field | No `Description`. Use `Item: { Name: string }`. |
| `SUMIT_COMPANY_ID` is a number at runtime | Env vars are strings. Coerce: `Number(SUMIT_COMPANY_ID)`. |
| `DocumentType` accepts string enum | Integer only. Pass `3`. SUMIT rejects string. |
| Boolean env vars work natively | Env vars are strings. Compare: `env.SUMIT_AUTHORISE_ONLY === 'true'`. |

---

## Test Cards (test terminal — same live API)

| Type | Card Number | Expiry | CVV | J5 hold |
|------|-------------|--------|-----|---------|
| Visa | `4557430402321333` | `05/2031` | `098` | ✓ credit |
| Mastercard | `5326105300985853` | `04/2026` | `934` | ✗ debit (returns 003) |
| Mastercard (alt) | `5326107300020772` | `05/2031` | `033` | ✓ credit |
| Amex | `375510390507999` | `04/2026` | `551` | ✓ credit |

**Recommended for E2E:** Visa `4557430402321333` / `05/2031` / `098`.
**For J5/J4 hold-capture testing:** use Visa, MC alt, or Amex (credit-class). MC primary is debit, rejects holds — useful for testing rejection path.

---

## Support

- Dashboard: `app.sumit.co.il`
- Email: `support@sumit.co.il` (Hebrew, same-day)
- Swagger: from dashboard or `POST /swagger`

---

## Learned Rules

### no-sumit-sandbox
One live API (`https://api.sumit.co.il`). Testing uses test terminal — same URL, different config. No feature-flag gates or sandbox conditionals. Refund path (negative-amount charge) works live.

### probe-multi-card-before-escalating
SUMIT/Shva rejection codes → test ALL 4 standard test cards before assuming terminal misconfiguration. Card-class issues (debit vs credit) = mixed results; terminal config issues = uniform failure. No SUMIT ticket from single failing card.

### prefer-multivendorcharge-for-capture
J5 → J4 capture: prefer `POST /billing/payments/multivendorcharge/` with `CreditCardAuthNumber + PaymentMethod.CreditCard_Token` over raw `/creditguy/gateway/transaction/` (`ParamJ:4`). multivendorcharge bundles vendor split + accounting docs; raw path needs separate doc creation.

### j5-only-on-credit-cards
J5 holds (תפיסת מסגרת) credit cards only. Israeli debit → code 003 from issuer. Reject debit at commit for deferred-capture flows (group deals, escrow, deposits). Detect via J2 + check response code.

### auth-number-is-single-use
Each J5 returns one `AuthNumber`. Capture consumes it. Dedup by AuthNumber, track `j5_attempt_count` on retries. Never reuse `AuthNumber`.