> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runpayments.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runpayments.io/_mcp/server.

# Terminal API Overview

The Terminal API lets your POS system accept card-present transactions on compatible card readers. Customers swipe, dip, or tap their card, and the API returns a payment account token and the authorized amount. You don't need Runner.js to tokenize card-present payments.

There are two ways to take a payment:

* **Read, then charge.** Call [Initiate a card read](/reference/terminal-api/card-read/read-card) to tokenize the card, then send the token and amount to the Payments API [Charge](/reference/payments-api/process-charge) endpoint.
* **Charge in one call.** Call [Charge a card](/reference/terminal-api/charge-card/initiate-charge) to read the card, optionally capture a signature, charge, and optionally print and email a receipt.

## Supported devices

| Device             | Signature capture | Receipt printing |
| ------------------ | ----------------- | ---------------- |
| Clover Flex        | Yes               | Yes              |
| Clover Mini        | Yes               | Yes              |
| Clover Flex Pocket | Yes               | No               |
| Clover Compact     | Yes               | No               |
| Ingenico Link 2500 | No                | No               |
| Ingenico Lane 3600 | No                | No               |
| Ingenico Lane 7000 | Yes               | No               |
| Ingenico Lane 8000 | Yes               | No               |

Capabilities are reported per terminal. Check the `capabilities` object from [List terminals](/reference/terminal-api/terminals/list-terminals) rather than relying on the device model.

## Setup

> **Request a development account**
>
> Send all UAT requests to [integrations@runpayments.io](mailto:integrations@runpayments.io).

| Variable    | Value                                                                                    |
| ----------- | ---------------------------------------------------------------------------------------- |
| Base URL    | `https://javelin.runpayments.io`                                                         |
| `device_id` | The terminal's Hardware Serial Number (HSN), provided by your Integration Delivery lead. |

## Authentication

All endpoints require a Payments API key as a bearer token:

```
Authorization: Bearer <api_key>
```

Use your `api_key` and `refresh_token` to request a fresh key from [Refresh API Keys](/reference/authentication/refresh-api-keys).

### ISV tokens

If your bearer token is an ISV token acting on behalf of a merchant, pass that merchant's `mid` as a query parameter (for [Send a receipt](/reference/terminal-api/receipt/send-receipt), it goes in the request body). If the `mid` can't be resolved to an authorized account, the request returns `400`.

## Rate limiting

All Terminal API endpoints share a per-credential rate limit. Requests over the limit return `429 Too Many Requests`.

## How interactions work

`read-card` and `charge-card` are asynchronous. Each returns `202 Accepted` with an `interaction_id` right away, while the terminal prompts the cardholder. Interactions expire 3 minutes after they're created.

#### Start the interaction

Call `POST /api/terminal/{device_id}/read-card` or `POST /api/terminal/{device_id}/charge-card`. The terminal session connects automatically if one isn't active.

#### Get the result

Poll the matching status endpoint until `status` is no longer `pending`, or subscribe over WebSocket for real-time updates.

#### Handle the outcome

For `read-card`, send `result.token` and the amount to the Payments API. For `charge-card`, check `result.result` for approval.

### Real-time updates over WebSocket

Instead of polling, you can receive status updates over WebSocket:

1. Call [Generate WebSocket auth token](/reference/terminal-api/authentication-web-socket/generate-ws-token) to get a single-use token. It expires after 60 seconds.
2. Connect to `wss://javelin.runpayments.io/cable?token=<token>`.
3. Subscribe to the channel `terminal_job:{interaction_id}`.

### Example: read a card

```javascript
async function readCard(deviceId, apiKey, amount) {
  const response = await fetch(
    `https://javelin.runpayments.io/api/terminal/${deviceId}/read-card`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({ amount, confirm_amount: true }),
    }
  );

  if (response.status !== 202) {
    throw new Error(`Card read failed to start: ${response.status}`);
  }

  // Interaction queued. Poll or subscribe for the result.
  const { interaction_id } = await response.json();
  return interaction_id;
}
```

> **A decline isn't a failure**
>
> When you use `charge-card`, a declined card still returns `status: "succeeded"`, because the workflow completed as requested. Always check `result.result` (`A` = Approved, `B` = Retry, `C` = Declined) before showing an approval. See [Get charge status](/reference/terminal-api/charge-card/get-charge-card-status).