> ## Documentation Index
> Fetch the complete documentation index at: https://docs.moduluslabs.io/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP Endpoints

> Complete reference for Terminal Gateway HTTP API endpoints

## Overview

The HTTP API provides three endpoints for terminal and payment management:

| Method | Endpoint                              | Description              |
| ------ | ------------------------------------- | ------------------------ |
| GET    | `/v1/terminals`                       | List available terminals |
| POST   | `/v1/terminals/{terminalId}/payments` | Initiate a payment       |
| GET    | `/v1/transactions/{transactionId}`    | Check transaction status |

## Base URL

```text theme={null}
https://{your-api-endpoint}/v1
```

<Note>
  Replace `{your-api-endpoint}` with your provisioned API endpoint. Contact [support@moduluslabs.io](mailto:support@moduluslabs.io) to obtain your endpoint URL.
</Note>

***

## GET /v1/terminals

Retrieve all active terminals in your group.

### Headers

| Header        | Required | Description           |
| ------------- | -------- | --------------------- |
| `x-api-key`   | Yes      | Your API key          |
| `x-timestamp` | Yes      | ISO 8601 timestamp    |
| `x-signature` | Yes      | HMAC-SHA256 signature |

### Response (200 OK)

```json theme={null}
{
  "terminals": [
    {
      "connectionId": "abc123xyz",
      "terminalId": "TERM-001",
      "deviceId": "TERM-001",
      "connectedAt": "2024-01-15T10:30:00.000Z",
      "lastActivity": "2024-01-15T10:35:00.000Z",
      "status": "online",
      "metadata": {}
    }
  ],
  "count": 1,
  "timestamp": "2024-01-15T10:35:30.000Z"
}
```

### Response Fields

| Field                      | Type   | Description                                  |
| -------------------------- | ------ | -------------------------------------------- |
| `terminals`                | array  | List of available terminals                  |
| `terminals[].connectionId` | string | Unique connection identifier                 |
| `terminals[].terminalId`   | string | Primary identifier (`deviceId` if available) |
| `terminals[].deviceId`     | string | Stable device identifier (if configured)     |
| `terminals[].connectedAt`  | string | ISO 8601 timestamp of connection             |
| `terminals[].lastActivity` | string | ISO 8601 timestamp of last activity          |
| `terminals[].status`       | string | `online`, `offline`, or `reconnecting`       |
| `terminals[].metadata`     | object | Custom terminal metadata                     |
| `count`                    | number | Total number of terminals                    |
| `timestamp`                | string | ISO 8601 timestamp of the response           |

### Error Responses

| Status | Code             | Description                             |
| ------ | ---------------- | --------------------------------------- |
| 401    | `UNAUTHORIZED`   | Invalid or missing authentication       |
| 403    | `GROUP_MISMATCH` | Only desktop devices can list terminals |
| 500    | `INTERNAL_ERROR` | Server error                            |

### Code Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://{your-api-endpoint}/v1/terminals" \
    -H "x-api-key: your-api-key" \
    -H "x-timestamp: 2024-01-15T10:30:00.000Z" \
    -H "x-signature: <computed-signature>"
  ```

  ```javascript Node.js theme={null}
  async function getTerminals() {
    const path = '/v1/terminals';
    const headers = generateAuthHeaders('GET', path);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'GET',
      headers
    });

    return response.json();
  }
  ```

  ```python Python theme={null}
  def get_terminals():
      path = '/v1/terminals'
      headers = generate_auth_headers('GET', path)

      response = requests.get(f'{BASE_URL}{path}', headers=headers)
      return response.json()
  ```
</CodeGroup>

***

## POST /v1/terminals/{terminalId}/payments

Initiate a payment to a specific terminal. By default, this endpoint uses long-polling and waits up to **90 seconds** for the terminal to respond. Alternatively, set `webhookMode: true` for immediate response with asynchronous notification via webhook.

### Path Parameters

<ParamField path="terminalId" type="string" required>
  The terminal identifier. Resolved as `deviceId` first, falling back to `connectionId`. See [Terminal ID Resolution](../concepts#terminal-id-resolution).
</ParamField>

<Note>
  The `terminalId` is resolved as a `deviceId` first, falling back to `connectionId` for legacy integrations. See [Terminal ID Resolution](../concepts#terminal-id-resolution) for details.
</Note>

### Headers

| Header         | Required | Description           |
| -------------- | -------- | --------------------- |
| `Content-Type` | Yes      | `application/json`    |
| `x-api-key`    | Yes      | Your API key          |
| `x-timestamp`  | Yes      | ISO 8601 timestamp    |
| `x-signature`  | Yes      | HMAC-SHA256 signature |

### Request Body

<ParamField body="transactionId" type="string">
  Unique transaction identifier. Auto-generated if not provided.
</ParamField>

<ParamField body="amount" type="string | number" required>
  Payment amount.
</ParamField>

<ParamField body="currency" type="string" required>
  ISO 4217 currency code (e.g., `"USD"`, `"EUR"`).
</ParamField>

<ParamField body="paymentMethod" type="string" required>
  Payment method: `"CARD"`, `"CASH"`, `"MOBILE"`, or `"OTHER"`.
</ParamField>

<ParamField body="products" type="array">
  Array of products in the transaction.

  <Expandable title="Product fields">
    <ParamField body="id" type="string" required>
      Product identifier
    </ParamField>

    <ParamField body="name" type="string" required>
      Product name
    </ParamField>

    <ParamField body="price" type="string | number" required>
      Unit price
    </ParamField>

    <ParamField body="quantity" type="number" required>
      Quantity
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="customerInfo" type="object">
  Customer information.

  <Expandable title="CustomerInfo fields">
    <ParamField body="customerId" type="string">
      Customer identifier
    </ParamField>

    <ParamField body="email" type="string">
      Customer email
    </ParamField>

    <ParamField body="phone" type="string">
      Customer phone
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="metadata" type="object">
  Custom key-value pairs for your use.
</ParamField>

<ParamField body="webhookMode" type="boolean" default="false">
  When `true`, disables 90-second long-polling and returns `202 Accepted` immediately. The payment result will be delivered via webhook to your configured endpoints. Requires at least one [webhook endpoint](./webhooks-setup) to be configured.
</ParamField>

### Request Example

```json theme={null}
{
  "transactionId": "TXN-20240115-001",
  "amount": "99.99",
  "currency": "USD",
  "paymentMethod": "CARD",
  "products": [
    {
      "id": "PROD-001",
      "name": "Widget",
      "price": "99.99",
      "quantity": 1
    }
  ],
  "customerInfo": {
    "customerId": "CUST-123",
    "email": "customer@example.com"
  },
  "metadata": {
    "orderId": "ORD-12345"
  }
}
```

### Response (200 OK) - Success

```json theme={null}
{
  "transactionId": "TXN-20240115-001",
  "status": "SUCCESS",
  "paymentResponse": {
    "transactionId": "TXN-20240115-001",
    "status": "SUCCESS",
    "amount": "99.99",
    "currency": "USD",
    "paymentMethod": "CARD",
    "authorizationCode": "AUTH123456",
    "receiptData": "...",
    "timestamp": "2024-01-15T10:37:30.000Z"
  },
  "timestamp": "2024-01-15T10:37:30.000Z"
}
```

### Response (202 Accepted) - Webhook Mode

Returned when `webhookMode: true`. The payment has been sent to the terminal and results will be delivered via webhook:

```json theme={null}
{
  "transactionId": "TXN-20240115-001",
  "status": "ACCEPTED",
  "message": "Payment sent to terminal. Result will be delivered via webhook.",
  "timestamp": "2024-01-15T10:36:00.000Z"
}
```

<Note>
  When using webhook mode, you must have at least one [webhook endpoint](./webhooks-setup) configured. The payment result will be delivered to your endpoints as a `payment.completed`, `payment.failed`, `payment.cancelled`, or `payment.timeout` event.
</Note>

### Response (202 Accepted) - Terminal Disconnected

Returned when the terminal disconnects during payment processing (standard mode only):

```json theme={null}
{
  "transactionId": "TXN-20240115-001",
  "status": "PENDING",
  "error": {
    "code": "TERMINAL_CONNECTION_ERROR",
    "message": "Terminal disconnected during payment processing"
  },
  "timestamp": "2024-01-15T10:37:30.000Z"
}
```

### Response (504 Gateway Timeout)

Returned when the terminal doesn't respond within 90 seconds:

```json theme={null}
{
  "error": {
    "code": "TIMEOUT",
    "message": "Terminal did not respond within 90 seconds",
    "details": {
      "note": "Transaction may still be processed. Check status via GET /v1/transactions/{id}"
    }
  },
  "transactionId": "TXN-20240115-001",
  "timestamp": "2024-01-15T10:39:00.000Z"
}
```

<Warning>
  After a timeout, always check the transaction status using `GET /v1/transactions/{transactionId}`. The payment may have completed on the terminal after the HTTP timeout.
</Warning>

### Error Responses

| Status | Code                        | Description                                                |
| ------ | --------------------------- | ---------------------------------------------------------- |
| 400    | `INVALID_REQUEST`           | Invalid request body or missing fields                     |
| 400    | `INVALID_AMOUNT`            | Amount doesn't match products total                        |
| 400    | `NO_WEBHOOK_ENDPOINTS`      | `webhookMode: true` requires at least one webhook endpoint |
| 401    | `UNAUTHORIZED`              | Invalid authentication                                     |
| 403    | `GROUP_MISMATCH`            | Terminal not in your group                                 |
| 404    | `TERMINAL_NOT_FOUND`        | Terminal not found                                         |
| 409    | `PAYMENT_IN_PROGRESS`       | Another payment is being processed                         |
| 502    | `TERMINAL_CONNECTION_ERROR` | Failed to communicate with terminal                        |
| 503    | `TERMINAL_OFFLINE`          | Terminal is offline                                        |
| 504    | `TIMEOUT`                   | Terminal did not respond in time                           |

### Code Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://{your-api-endpoint}/v1/terminals/TERM-001/payments" \
    -H "Content-Type: application/json" \
    -H "x-api-key: your-api-key" \
    -H "x-timestamp: 2024-01-15T10:36:00.000Z" \
    -H "x-signature: <computed-signature>" \
    -d '{
      "transactionId": "TXN-20240115-001",
      "amount": "99.99",
      "currency": "USD",
      "paymentMethod": "CARD",
      "products": [
        {
          "id": "PROD-001",
          "name": "Widget",
          "price": "99.99",
          "quantity": 1
        }
      ],
      "metadata": {
        "orderId": "ORD-12345"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  async function createPayment(terminalId, paymentData) {
    const path = `/v1/terminals/${terminalId}/payments`;
    const headers = generateAuthHeaders('POST', path, paymentData);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'POST',
      headers,
      body: JSON.stringify(paymentData)
    });

    return response.json();
  }

  // Usage
  const result = await createPayment('TERM-001', {
    transactionId: `TXN-${Date.now()}`,
    amount: '99.99',
    currency: 'USD',
    paymentMethod: 'CARD',
    products: [
      { id: 'PROD-001', name: 'Widget', price: '99.99', quantity: 1 }
    ],
    metadata: { orderId: 'ORD-12345' }
  });
  ```

  ```python Python theme={null}
  def create_payment(terminal_id: str, payment_data: dict):
      path = f'/v1/terminals/{terminal_id}/payments'
      headers = generate_auth_headers('POST', path, payment_data)

      response = requests.post(
          f'{BASE_URL}{path}',
          headers=headers,
          json=payment_data
      )
      return response.json()

  # Usage
  result = create_payment('TERM-001', {
      'transactionId': f'TXN-{int(time.time() * 1000)}',
      'amount': '99.99',
      'currency': 'USD',
      'paymentMethod': 'CARD',
      'products': [
          {'id': 'PROD-001', 'name': 'Widget', 'price': '99.99', 'quantity': 1}
      ],
      'metadata': {'orderId': 'ORD-12345'}
  })
  ```
</CodeGroup>

***

## GET /v1/transactions/{transactionId}

Retrieve the status of a transaction. Useful for reconciliation or checking status after a timeout.

### Path Parameters

<ParamField path="transactionId" type="string" required>
  The transaction ID to query.
</ParamField>

### Headers

| Header        | Required | Description           |
| ------------- | -------- | --------------------- |
| `x-api-key`   | Yes      | Your API key          |
| `x-timestamp` | Yes      | ISO 8601 timestamp    |
| `x-signature` | Yes      | HMAC-SHA256 signature |

### Response (200 OK)

```json theme={null}
{
  "transactionId": "TXN-20240115-001",
  "status": "COMPLETED",
  "request": {
    "transactionId": "TXN-20240115-001",
    "amount": "99.99",
    "currency": "USD",
    "paymentMethod": "CARD"
  },
  "response": {
    "transactionId": "TXN-20240115-001",
    "status": "SUCCESS",
    "amount": "99.99",
    "currency": "USD",
    "paymentMethod": "CARD",
    "authorizationCode": "AUTH123456",
    "timestamp": "2024-01-15T10:37:30.000Z"
  },
  "createdAt": "2024-01-15T10:37:00.000Z",
  "updatedAt": "2024-01-15T10:37:30.000Z",
  "completedAt": "2024-01-15T10:37:30.000Z",
  "timestamp": "2024-01-15T10:40:00.000Z"
}
```

### Response Fields

| Field              | Type   | Description                           |
| ------------------ | ------ | ------------------------------------- |
| `transactionId`    | string | Unique transaction identifier         |
| `status`           | string | Transaction status (see below)        |
| `request`          | object | Original payment request              |
| `response`         | object | Payment response (if completed)       |
| `createdAt`        | string | ISO 8601 timestamp of creation        |
| `updatedAt`        | string | ISO 8601 timestamp of last update     |
| `completedAt`      | string | ISO 8601 timestamp of completion      |
| `terminalDeviceId` | string | Terminal device ID (if available)     |
| `voidedAt`         | string | Timestamp when voided (if applicable) |
| `voidReason`       | string | Reason for voiding (if voided)        |
| `timestamp`        | string | ISO 8601 timestamp of this response   |

### Transaction Status Values

| Status               | Description                                        |
| -------------------- | -------------------------------------------------- |
| `PENDING`            | Transaction created, awaiting terminal response    |
| `COMPLETED`          | Transaction completed successfully                 |
| `FAILED`             | Transaction failed (see error details)             |
| `CANCELLED`          | Transaction was cancelled                          |
| `AWAITING_RECONNECT` | Terminal disconnected, waiting for reconnection    |
| `VOIDED`             | Payment was voided (terminal reconnected too late) |

### Error Responses

| Status | Code                    | Description                   |
| ------ | ----------------------- | ----------------------------- |
| 401    | `UNAUTHORIZED`          | Invalid authentication        |
| 403    | `GROUP_MISMATCH`        | Transaction not in your group |
| 404    | `TRANSACTION_NOT_FOUND` | Transaction not found         |
| 500    | `INTERNAL_ERROR`        | Server error                  |

### Code Example

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://{your-api-endpoint}/v1/transactions/TXN-20240115-001" \
    -H "x-api-key: your-api-key" \
    -H "x-timestamp: 2024-01-15T10:40:00.000Z" \
    -H "x-signature: <computed-signature>"
  ```

  ```javascript Node.js theme={null}
  async function getTransaction(transactionId) {
    const path = `/v1/transactions/${transactionId}`;
    const headers = generateAuthHeaders('GET', path);

    const response = await fetch(`${BASE_URL}${path}`, {
      method: 'GET',
      headers
    });

    return response.json();
  }

  // Usage
  const txn = await getTransaction('TXN-20240115-001');
  console.log('Status:', txn.status);
  ```

  ```python Python theme={null}
  def get_transaction(transaction_id: str):
      path = f'/v1/transactions/{transaction_id}'
      headers = generate_auth_headers('GET', path)

      response = requests.get(f'{BASE_URL}{path}', headers=headers)
      return response.json()

  # Usage
  txn = get_transaction('TXN-20240115-001')
  print(f"Status: {txn['status']}")
  ```
</CodeGroup>

***

## Rate Limiting

The API implements rate limiting to ensure service stability. Current limits are applied per API key.

| Limit Type          | Value           |
| ------------------- | --------------- |
| Requests per minute | Contact support |
| Concurrent payments | 1 per terminal  |

If you exceed rate limits, you'll receive a `429 Too Many Requests` response:

```json theme={null}
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Please slow down."
  },
  "timestamp": "2024-01-15T10:40:00.000Z"
}
```

<Note>
  Contact [support@moduluslabs.io](mailto:support@moduluslabs.io) if you require higher rate limits for your integration.
</Note>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Authentication Issues">
    **Common causes:**

    * Invalid API key or secret
    * System clock not synchronized (timestamp must be within 5 minutes)
    * Incorrect SHA256 body hash computation (must be hex-encoded)
    * String-to-sign format mismatch (check newline characters)

    **Debug steps:**

    1. Verify your API key and secret are correct
    2. Check your system clock is synchronized with NTP
    3. Log the string-to-sign and compare with documentation
    4. Ensure body hash is computed on the exact JSON string sent
  </Accordion>

  <Accordion title="Payment Timeouts">
    **What to do after a 504 timeout:**

    1. Do not retry the payment immediately
    2. Call `GET /v1/transactions/{transactionId}` to check actual status
    3. The payment may have completed on the terminal
    4. Only retry if status is `FAILED` or `CANCELLED`

    **Prevention:**

    * Verify terminal is online before initiating payments
    * Monitor terminal status with `GET /v1/terminals`
  </Accordion>

  <Accordion title="Terminal Not Found">
    **Causes:**

    * Terminal is offline
    * Using wrong terminal ID (connectionId vs deviceId)
    * Terminal in different group

    **Solutions:**

    1. Refresh terminal list with `GET /v1/terminals`
    2. Use `deviceId` instead of `connectionId`
    3. Verify API key matches terminal's group
  </Accordion>

  <Accordion title="Payment In Progress (409)">
    **Cause:** Another payment is already being processed on the terminal.

    **Solution:** Wait for the current payment to complete before initiating a new one. Terminals can only process one payment at a time.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="./quickstart">
    Step-by-step integration guide
  </Card>

  <Card title="Webhook Setup" icon="bell" href="./webhooks-setup">
    Configure webhook endpoints
  </Card>

  <Card title="Authentication" icon="key" href="../authentication">
    HMAC signature details
  </Card>

  <Card title="Data Types" icon="brackets-curly" href="../reference">
    Shared data type reference
  </Card>
</CardGroup>
