# Data Models

Complete data model reference for all API objects.

---

## Transaction

The transaction record returned by `GET /api/v1/transactions/:id`, `GET /api/v1/public/transaction/:id` and the transaction lists. Ids are UUIDs; `transactionId` is the human-readable reference. The Public API `POST /api/v1/public/transaction` answers with a PascalCase summary instead (see [Transactions](../core/transactions.md)).

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440020",
  "transactionId": "TXN-1776037573138-56123064",
  "merchantId": "550e8400-e29b-41d4-a716-446655440000",
  "type": "sale",
  "status": "approved",
  "amount": "153.75",
  "baseAmount": "150.00",
  "tipAmount": "0.00",
  "taxAmount": "0.00",
  "convenienceFee": "3.75",
  "currency": "USD",
  "paymentMethod": "Card",
  "cardBrand": "visa",
  "cardLast4": "1111",
  "authCode": "123456",
  "responseCode": "100",
  "responseText": "Approved",
  "avsResponse": "Y",
  "cvvResponse": "M",
  "gatewayName": "NMI",
  "gatewayTransactionId": "8812734455",
  "originalTransactionId": null,
  "billingCustomerName": "John Doe",
  "billingEmail": "john@example.com",
  "billingAddress": "123 Main St",
  "billingCity": "New York",
  "billingState": "NY",
  "billingPostalCode": "10001",
  "billingCountry": "US",
  "orderId": "1042",
  "invoiceNumber": null,
  "requestOrigin": "api",
  "branchId": null,
  "batchId": null,
  "createdAt": "2026-01-28T12:00:00.000Z",
  "updatedAt": "2026-01-28T12:00:00.000Z"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `id` | string (UUID) | Use in `/transactions/:id` paths |
| `type` | string | `sale`, `auth`, `void`, `refund`, `capture`, `cancel_capture`, `credit` |
| `status` | string | `pending`, `approved`, `declined`, `settled`, `voided`, `refunded`, `error`, `reversed` (ACH return), `processing` (ACH in flight) |
| `amount` | string (decimal) | Total charged: `baseAmount` + `tipAmount` + `taxAmount` + `convenienceFee`. All money fields on a read are decimal strings such as `"153.75"` — parse them before doing arithmetic |
| `paymentMethod` | string | `Card`, `ACH`, `Cash`, `GiftCard` |
| `responseCode` | string | NMI-style code; `100` = approved (see [Error Codes](./error-codes.md)) |
| `originalTransactionId` | string \| null | For void, refund and capture rows, the sale / auth they act on |
| `requestOrigin` | string | Where it came from: `vt`, `api`, `ipos`, `amp`, `woocommerce` and others |

The record also holds shipping fields, 3DS results, chargeback fields (`chargeback`, `resolution`) and fee-breakdown fields; card numbers and CVV are never stored or returned.

---

## Subscription

A recurring subscription (`/api/v1/recurring`). The response is the stored record.

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440040",
  "merchantId": "550e8400-e29b-41d4-a716-446655440000",
  "customerId": "550e8400-e29b-41d4-a716-446655440010",
  "description": "Gold membership",
  "status": "active",
  "recurringAmount": "99.00",
  "period": "Monthly",
  "recurringDate1": "2026-01-01",
  "recurringDate2": null,
  "maximumOccurrence": "NoExpire",
  "suggestedMode": "Card",
  "nextPaymentDate": "2026-02-01",
  "lastPaymentDate": "2026-01-01",
  "successCount": 1,
  "failedPaymentCount": 0,
  "maxRetryAttempts": 3,
  "totalCollectedAmount": "99.00",
  "createdAt": "2026-01-01T00:00:00.000Z"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `active`, `scheduled`, `processing`, `paused`, `cancelled`, `expired`, `pending` (awaiting authorization), `completed` |
| `period` | string | `Weekly`, `BiWeekly`, `SemiMonthly`, `Monthly`, `Quarterly`, `Annually`, `Custom` |
| `maximumOccurrence` | string | `1`-`999` or `NoExpire` |
| `suggestedMode` | string | `Card` or `ACH` |

Installment plans (`/api/v1/billing/payment-plans`) use a different model: `planName`, `totalAmount`, `paymentFrequency`, and the statuses `draft`, `pending_approval`, `active`, `paused`, `completed`, `cancelled`, `defaulted`. See [Payment Plans](../recurring/payment-plans.md).

---

## Invoice

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440050",
  "invoiceNumber": "INV-2026-000042",
  "linkId": "k3v9x2m7q1",
  "shortLink": "https://short.example.com/k3v9x2",
  "merchantId": "550e8400-e29b-41d4-a716-446655440000",
  "customerId": "550e8400-e29b-41d4-a716-446655440010",
  "customerName": "John Doe",
  "customerEmail": "john@example.com",
  "customerPhone": "+12125550123",
  "status": "sent",
  "lineItems": [
    {
      "id": "li_1",
      "name": "Web Design Services",
      "quantity": 1,
      "unitPrice": 500.00,
      "amount": 500.00
    }
  ],
  "subtotal": "500.00",
  "taxAmount": "0.00",
  "discountAmount": "0.00",
  "convenienceFee": "0.00",
  "totalAmount": "500.00",
  "amountPaid": "0.00",
  "amountDue": "500.00",
  "currency": "USD",
  "allowPartialPayment": false,
  "issueDate": "2026-01-28",
  "dueDate": "2026-02-15",
  "paidAt": null,
  "createdAt": "2026-01-28T12:00:00.000Z"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | `draft`, `sent`, `viewed`, `partial`, `paid`, `overdue`, `cancelled`, `pending`, `expired` (legacy; invoices no longer expire) |
| `subtotal`, `taxAmount`, `discountAmount`, `convenienceFee`, `totalAmount`, `amountPaid`, `amountDue` | string (decimal) | Decimal strings on reads (`GET`, list); the create and payment responses return them as numbers |
| `discountAmount` | string (decimal) | The only discount applied to the total. Line-item `discount` values are not subtracted by the server, so sum them into `discountAmount` yourself |
| `lineItems[]` | array | `id`, `name`, `description`, `quantity`, `unit`, `unitPrice`, `discount`, `discountType`, `taxRate`, `amount` |
| `paymentHistory[]` | array | `transactionId`, `amount`, `paidAt`, `paymentMethod`, `notes`, `isExternal`, `balanceAfter` |
| `metadata` | object | Includes `location` (the branch **name**), `acceptCards`, `acceptAch` |

---

## Customer

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440010",
  "merchantId": "550e8400-e29b-41d4-a716-446655440000",
  "customerName": "John Doe",
  "customerType": "individual",
  "email": "john@example.com",
  "phoneNumber": "+12125550123",
  "address": "123 Main St",
  "city": "New York",
  "state": "NY",
  "postalCode": "10001",
  "country": "US",
  "blockAch": false,
  "paymentMethodCount": 1,
  "createdAt": "2026-01-01T00:00:00.000Z",
  "updatedAt": "2026-01-01T00:00:00.000Z"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `customerName` | string | Required. The Public API also accepts `firstName` + `lastName` and joins them |
| `customerType` | string | `individual` or `business` |
| `phoneNumber` | string | The Public API also accepts `phone` |

### Payment method

Returned by `GET /customers/:id/payment-methods`.

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440060",
  "methodType": "card",
  "cardBrand": "visa",
  "last4Digits": "1111",
  "expirationMonth": "12",
  "expirationYear": "2027",
  "cardholderName": "John Doe",
  "isDefault": true,
  "isVerified": true,
  "paymentMethodStatus": "active"
}
```

ACH methods carry `bankName`, `accountLast4` and `routingLast4` instead of the card fields. Full account numbers are never returned.

---

## Status Values

| Object | Values |
|--------|--------|
| Transaction `status` | `pending`, `approved`, `declined`, `settled`, `voided`, `refunded`, `error`, `reversed`, `processing` |
| Transaction `type` | `sale`, `auth`, `void`, `refund`, `capture`, `cancel_capture`, `credit` |
| Subscription `status` | `active`, `scheduled`, `processing`, `paused`, `cancelled`, `expired`, `pending`, `completed` |
| Invoice `status` | `draft`, `sent`, `viewed`, `partial`, `paid`, `overdue`, `cancelled`, `pending`, `expired` |
| Merchant `status` | `active`, `closed`, `refund_only`, `restricted` |
| Device `status` | `PENDING`, `REGISTERED`, `ACTIVE`, `INACTIVE`, `SUSPENDED`, `ARCHIVED` |
| Settlement `status` | `calculating`, `calculated`, `reconciling`, `reconciled`, `discrepancy`, `approved`, `payout_processing`, `paid`, `failed` |
| Batch payout `status` | `draft`, `pending_review`, `approved`, `processing`, `completed`, `cancelled` |
| Refund queue `status` | See [Refund Queue](../advanced/refund-queue.md) |

---

## UserMerchant

One entry of the merchant picker returned by `GET /api/v1/auth/my-merchants`.

```json
{
  "merchantId": "550e8400-e29b-41d4-a716-446655440000",
  "companyName": "Acme Coffee",
  "role": "merchant_admin",
  "status": "active",
  "deviceRegistration": {
    "hasActiveDevice": true,
    "activeDeviceCount": 1,
    "device": {
      "deviceId": "550e8400-e29b-41d4-a716-446655440001",
      "deviceLabel": "Counter 1",
      "tpnLast4": "4651",
      "status": "ACTIVE",
      "lastActiveAt": "2026-08-10T09:14:02.000Z"
    }
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `status` | string | Merchant status — `active`, `closed`, `refund_only`, `restricted` |
| `deviceRegistration` | object \| absent | Omitted when the caller passed `includeDeviceStatus=false` |
| `deviceRegistration.hasActiveDevice` | boolean | This user has at least one `ACTIVE` device registered at this merchant |
| `deviceRegistration.activeDeviceCount` | number | How many, counting only devices this user registered |
| `deviceRegistration.device` | object \| null | Most recently active of those devices; `null` when there are none |
| `device.tpnLast4` | string | Last 4 digits of the TPN — display only; the full TPN is never returned here |

Devices registered by other staff at the same merchant are never counted or returned.

---

## Device (ownership fields)

Selected fields of the device object relevant to app pairing — full model in [Devices](../merchants/devices.md).

| Field | Type | Description |
|-------|------|-------------|
| `registeredByUserId` | string \| null | Merchant user who registered the app against this device. Only this user may re-register it while `ACTIVE`. `null` for devices registered before the field existed |
| `registeredAt` | string \| null | First registration timestamp; preserved across re-registrations |
| `lastActiveAt` | string \| null | Last authenticated call from the device |
| `createdBy` / `updatedBy` | string \| null | Portal admin who provisioned or edited the record — not the app user |

---

## Pagination

List endpoints return a page descriptor next to `data`. The exact shape differs by service; the common form is `meta`:

```json
{
  "page": 1,
  "limit": 50,
  "total": 1234
}
```

Subscriptions return `total`, `page`, `perPage` and `totalPages` beside `data`. The refund queue returns `data: { entries, total, page, limit }`.

---

## Error

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid card number"
  }
}
```

There is no `field` or `details` member; the message names the field. API-key failures on the Public API use a string `error`, and a few services return only `message`. See [Error Codes](./error-codes.md).

---

**Next:** [View all endpoints →](./endpoints.md)
