# Cloud Accept API (/cs/in-store/integration/cloud_accept_api)





<div className="bg-background relative h-auto w-full overflow-hidden rounded-lg border mt-8">
  <FlickeringGrid className="absolute inset-0 z-0 size-full" squareSize="4" gridGap="6" maxOpacity="0.1" flickerChance="0.1" height="800" width="2000" />

  <CloudAcceptDiagram />
</div>

Cloud Accept API umožňuje vaší elektronické registrační pokladně (ECR) nebo backendu POS spustit platbu na zařízení
Tapaya přes cloud. Váš systém nikdy nekomunikuje se zařízením přímo: odešle požadavek na platbu do Tapaya, Tapaya
upozorní spárované zařízení a zařízení zobrazí zákazníkovi platební obrazovku. Informace o průběhu k vám přicházejí
jako webhooky, případně si je zjišťujete dotazováním (pollingem).

## Výběr způsobu párování zařízení [#choose-how-to-pair-devices]

Každé zařízení, na které posíláte platby, potřebuje `externalDeviceId`: název, pod kterým jej zná váš systém, například
`till-01`. Přiřadit jej můžete jedním ze dvou způsobů:

<Cards>
  <VisualCard title="Z vaší aplikace" visual="<Layers />" href="#assign-from-your-app" description="Vaše aplikace přiřadí externalDeviceId prostřednictvím Accept SDK na zařízení." />

  <VisualCard title="QR kód z Tapaya Platform" visual="<QrCode />" href="#scan-a-qr-code" description="Obchodník zobrazí QR kód v Tapaya Platform a naskenuje jej aplikací pro obchodníky." />
</Cards>

## Než začnete [#before-you-start]

1. **Získejte svůj Server Secret Token** na [stránce API Keys](https://platform.tapaya.com/api-keys/). Níže jej
   vyměníte za přístupový token omezený na obchodníka (viz [autentizace Platform API](/platform-api#authentication)).
2. **Zaregistrujte obchodníka** a uchovejte si jeho `merchantToken`. Viz
   [Registrace nového obchodníka](./backend_integration#register-a-new-merchant).
3. **Připravte každé zařízení** s aplikací Tapaya přihlášenou za daného obchodníka, aby je bylo možné spárovat
   a mohlo přijímat požadavky.

<Callout title="Jeden token, jeden obchodník" type="info">
  Každé volání Cloud Accept API je omezeno právě na jednoho obchodníka. Chcete-li obsluhovat více obchodníků, získejte
  výměnou samostatný přístupový token pro každého z nich.
</Callout>

<section id="cloud-accept-steps">
  <div className="fd-steps [&_h3]:fd-step">
    ### Spárování zařízení [#pair-a-device]

    Párování přiřadí zařízení Tapaya `externalDeviceId`, takže je můžete najít při výpisu zařízení
    a zacílit na ně při spuštění platby.

    #### Přiřazení z vaší aplikace [#assign-from-your-app]

    Vaše aplikace zavolá na zařízení funkci Accept SDK, která mu přiřadí `externalDeviceId`. Ve všech
    voláních Cloud Accept API pro toto zařízení používejte stejný identifikátor.

    #### Naskenování QR kódu [#scan-a-qr-code]

    Obchodník otevře v Tapaya Platform postup párování, který zobrazí QR kód pro přiřazované
    `externalDeviceId`. Obchodník jej naskenuje aplikací Tapaya pro obchodníky na cílovém
    zařízení a aplikace párování dokončí.

    ### Výměna za přístupový token [#exchange-an-access-token]

    Vyměňte svůj Server Secret Token a `merchantToken` obchodníka za přístupový token pomocí
    `POST /ecr/auth`. `merchantToken` se odesílá pouze zde; všechna další volání odesílají jen
    `Authorization: Bearer <accessToken>`.

    <CodeBlockTabs defaultValue="curl" groupId="cloud-accept-client">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="curl">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="typescript">
          TypeScript
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="curl">
        ```bash
        curl -X 'POST' 'https://api.tapaya.com/ecr/auth' \
        -H 'Content-Type: application/json' \
        -H 'Authorization: Bearer REPLACE_ME' \
        -d '{
                "merchantToken": "unique_merchant_id_from_your_db"
            }'
        ```
      </CodeBlockTab>

      <CodeBlockTab value="typescript">
        ```typescript
        const res = await fetch('https://api.tapaya.com/ecr/auth', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${process.env.TAPAYA_SECRET_TOKEN}`,
            },
            body: JSON.stringify({ merchantToken: 'unique_merchant_id_from_your_db' }),
        });
        const { accessToken, expiresAt } = await res.json();
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ```json title="200 OK"
    {
        "accessToken": "...",
        "tokenType": "Bearer",
        "expiresAt": "2026-01-13T16:30:59Z"
    }
    ```

    | Kód   | Popis                                                  |
    | ----- | ------------------------------------------------------ |
    | `401` | Neplatný API token nebo API token s prošlou platností. |
    | `403` | Přístup k obchodníkovi není povolen.                   |

    **Obnovte token před `expiresAt`.** Zavolejte `POST /ecr/auth/refresh` s aktuálním, stále platným
    `accessToken` v hlavičce `Authorization`. Endpoint vrátí stejné tělo s novým tokenem; uložte si jej
    a nadále používejte už jen ten nový.

    <CodeBlockTabs defaultValue="curl" groupId="cloud-accept-client">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="curl">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="typescript">
          TypeScript
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="curl">
        ```bash
        curl -X 'POST' 'https://api.tapaya.com/ecr/auth/refresh' \
        -H 'Authorization: Bearer ACCESS_TOKEN'
        ```
      </CodeBlockTab>

      <CodeBlockTab value="typescript">
        ```typescript
        const res = await fetch('https://api.tapaya.com/ecr/auth/refresh', {
            method: 'POST',
            headers: { Authorization: `Bearer ${accessToken}` },
        });
        const renewed = await res.json(); // { accessToken, tokenType, expiresAt }
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    | Kód   | Popis                                                                                                   |
    | ----- | ------------------------------------------------------------------------------------------------------- |
    | `401` | Přístupový token je neplatný, vypršel nebo byl zneplatněn. Získejte nový výměnou přes `POST /ecr/auth`. |
    | `403` | K obchodníkovi již nemáte přístup. Nový token se nevydá.                                                |

    ### Vyhledání připraveného zařízení [#find-a-ready-device]

    Zařízení obchodníka vypište pomocí `GET /ecr/devices`. Přidáním `?connected=true` vynecháte
    odpojená zařízení; jedno konkrétní zařízení načtete pomocí `GET /ecr/devices/{externalDeviceId}`.

    <CodeBlockTabs defaultValue="curl" groupId="cloud-accept-client">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="curl">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="typescript">
          TypeScript
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="curl">
        ```bash
        curl 'https://api.tapaya.com/ecr/devices/till-01' \
        -H 'Authorization: Bearer ACCESS_TOKEN'
        ```
      </CodeBlockTab>

      <CodeBlockTab value="typescript">
        ```typescript
        const res = await fetch('https://api.tapaya.com/ecr/devices/till-01', {
            headers: { Authorization: `Bearer ${accessToken}` },
        });
        const device = await res.json();
        const canPay = device.availability === 'ready';
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ```json title="200 OK"
    {
        "externalDeviceId": "till-01",
        "name": "Front counter",
        "connected": true,
        "availability": "ready",
        "lastSeenAt": "2026-01-13T16:30:59Z",
        "updatedAt": "2026-01-13T16:30:59Z"
    }
    ```

    Platby posílejte pouze na zařízení, jehož `availability` je `ready`. Výpis je jen momentální snímek:
    dostupnost se znovu ověřuje při odeslání, takže i zařízení ve stavu `ready` může požadavek
    odmítnout. Viz [Dostupnost zařízení](#device-availability).

    ### Odeslání požadavku na platbu [#submit-a-payment-request]

    Částku odešlete na zařízení pomocí `POST /ecr/payment-requests`. Tapaya požadavek uloží a
    upozorní zařízení, které zobrazí platební obrazovku.

    <CodeBlockTabs defaultValue="curl" groupId="cloud-accept-client">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="curl">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="typescript">
          TypeScript
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="curl">
        ```bash
        curl -X 'POST' 'https://api.tapaya.com/ecr/payment-requests' \
        -H 'Content-Type: application/json' \
        -H 'Authorization: Bearer ACCESS_TOKEN' \
        -H 'Idempotency-Key: order-1042-attempt-1' \
        -d '{
                "externalDeviceId": "till-01",
                "amount": 12500,
                "tipAmount": 500,
                "currency": "CZK"
            }'
        ```
      </CodeBlockTab>

      <CodeBlockTab value="typescript">
        ```typescript
        const res = await fetch('https://api.tapaya.com/ecr/payment-requests', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${accessToken}`,
                'Idempotency-Key': 'order-1042-attempt-1',
            },
            body: JSON.stringify({
                externalDeviceId: 'till-01',
                amount: 12500, // 125.00 Kč
                tipAmount: 500, // 5.00 Kč
                currency: 'CZK',
            }),
        });
        const { requestId } = await res.json(); // 202 Accepted
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    **Parametry:**

    | Pole               | Typ       | Povinné | Popis                                                   |
    | ------------------ | --------- | ------- | ------------------------------------------------------- |
    | `externalDeviceId` | `string`  | ✓       | Spárované zařízení, které má platbu zobrazit.           |
    | `amount`           | `integer` | ✓       | Částka v nejmenší jednotce měny (`12500` je 125,00 Kč). |
    | `tipAmount`        | `integer` |         | Spropitné v nejmenší jednotce měny.                     |
    | `currency`         | `string`  | ✓       | Kód měny podle ISO 4217.                                |
    | `paymentToken`     | `string`  |         | Existující platba, ke které se má požadavek připojit.   |

    ```json title="202 Accepted"
    {
        "requestId": "...",
        "status": "pending",
        "paymentToken": null,
        "expiresAt": "2026-01-13T16:30:59Z"
    }
    ```

    <Callout title="202 není výsledek platby" type="warn">
      `202 Accepted` znamená, že Tapaya požadavek uložila. Neznamená to, že jej zařízení zaznamenalo
      ani že zákazník zaplatil. Cokoli, co selže později, se k vám dostane jako změna stavu.
    </Callout>

    **Zajistěte bezpečné opakování požadavků.** Hlavička `Idempotency-Key` je povinná. Opětovné použití klíče se stejným tělem
    vrátí původní požadavek místo spuštění druhé platby; opětovné použití s jiným tělem
    vrátí `409 idempotency_conflict`. Svažte klíč s prodejem a pokusem, například
    `order-1042-attempt-1`.

    ### Sledování výsledku [#track-the-result]

    Sledujte požadavek přes jeho [stavy](#request-statuses) pomocí webhooků, nebo pravidelně volejte
    `GET /ecr/payment-requests/{requestId}`. Jakmile požadavek dosáhne stavu `payment_created`, odpověď obsahuje
    `paymentToken` a průběh samotné platby hlásí události `payment.updated`.

    Chcete-li přijímat webhooky, zaregistrujte svůj HTTPS endpoint pomocí `POST /ecr/webhook-subscriptions`, nebo
    pro obchodníka nastavte callback URL a typy událostí v Tapaya Platform.

    <CodeBlockTabs defaultValue="curl" groupId="cloud-accept-client">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="curl">
          cURL
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="typescript">
          TypeScript
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="curl">
        ```bash
        curl -X 'POST' 'https://api.tapaya.com/ecr/webhook-subscriptions' \
        -H 'Content-Type: application/json' \
        -H 'Authorization: Bearer ACCESS_TOKEN' \
        -d '{
                "url": "https://integrator.example.com/webhooks/tapaya",
                "events": ["payment_request.updated", "payment.updated"]
            }'
        ```
      </CodeBlockTab>

      <CodeBlockTab value="typescript">
        ```typescript
        const res = await fetch('https://api.tapaya.com/ecr/webhook-subscriptions', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${accessToken}`,
            },
            body: JSON.stringify({
                url: 'https://integrator.example.com/webhooks/tapaya',
                events: ['payment_request.updated', 'payment.updated'],
            }),
        });
        const { subscriptionId, signingSecret } = await res.json(); // 201 Created
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    ```json title="201 Created"
    {
        "subscriptionId": "...",
        "url": "https://integrator.example.com/webhooks/tapaya",
        "events": ["payment_request.updated", "payment.updated"],
        "signingSecret": "..."
    }
    ```

    <Callout title="Uložte si signing secret" type="warn">
      `signingSecret` se vrací pouze při vytvoření odběru. Bezpečně si jej uložte; budete
      jej potřebovat k ověření podpisu příchozích webhooků.
    </Callout>

    Odběr patří integrátorovi a obchodníkovi, nikoli přístupovému tokenu, takže při obnovení tokenu
    zůstává zachován. Na každou událost odpovězte kódem `2xx`, jakmile ji přijmete.

    <Callout title="Před uzavřením prodeje ověřte výsledek" type="error">
      Webhooky jsou oznámení, nikoli doklad o platbě, a některý může dorazit pozdě, nebo nemusí dorazit vůbec. Nikdy
      neuzavírejte prodej pouze na základě webhooku:

      * **Když webhook dorazí,** ověřte konečný výsledek přes API, například pomocí
        [Načtení podrobností platby](/platform-api/reporting#retrieve-payment-details).
      * **Když webhook nedorazí** v časovém limitu vhodném pro váš pokladní proces, nejpozději však
        do `expiresAt` požadavku, zavolejte `GET /ecr/payment-requests/{requestId}`, abyste zjistili
        jeho aktuální stav, a poté ověřte platbu přes API, jak je uvedeno výše.
    </Callout>
  </div>
</section>

## Reference [#reference]

### Endpointy [#endpoints]

| Metoda   | Endpoint                                      | Popis                                                                                     |
| -------- | --------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `POST`   | `/ecr/auth`                                   | Vymění váš API token a `merchantToken` za přístupový token.                               |
| `POST`   | `/ecr/auth/refresh`                           | Obnoví dosud platný přístupový token.                                                     |
| `GET`    | `/ecr/devices`                                | Vypíše zařízení obchodníka. Volitelný filtr `connected=true`.                             |
| `GET`    | `/ecr/devices/{externalDeviceId}`             | Vrátí jedno zařízení a jeho aktuální dostupnost.                                          |
| `POST`   | `/ecr/payment-requests`                       | Uloží požadavek na platbu a upozorní zařízení. Vyžaduje `Idempotency-Key`.                |
| `GET`    | `/ecr/payment-requests/{requestId}`           | Vrátí stav požadavku, případný důvod odmítnutí a `paymentToken`.                          |
| `POST`   | `/ecr/webhook-subscriptions`                  | Zaregistruje HTTPS URL a typy událostí. Signing secret (podpisový klíč) vrátí jen jednou. |
| `GET`    | `/ecr/webhook-subscriptions`                  | Vypíše vaše odběry pro obchodníka, bez signing secretů.                                   |
| `DELETE` | `/ecr/webhook-subscriptions/{subscriptionId}` | Odstraní odběr.                                                                           |

### Dostupnost zařízení [#device-availability]

Oba endpointy pro zařízení vracejí `externalDeviceId`, `name`, `connected`, `availability`, `lastSeenAt` a
`updatedAt`.

| Hodnota       | Popis                                                                   |
| ------------- | ----------------------------------------------------------------------- |
| `ready`       | Připojeno a schopné přijmout a zobrazit požadavek.                      |
| `busy`        | Zpracovává platbu nebo má aktivní požadavek.                            |
| `unavailable` | Odpojeno, bez platné relace nebo neschopné zobrazit platební obrazovku. |

### Stavy požadavku [#request-statuses]

| Stav              | Popis                                                                   |
| ----------------- | ----------------------------------------------------------------------- |
| `pending`         | Uložen a čeká, až jej zařízení přijme.                                  |
| `acknowledged`    | Zařízení jej obdrželo; platba ještě nemusí existovat.                   |
| `payment_created` | Platba a její `paymentToken` existují. Další průběh sledujte na platbě. |
| `rejected`        | Zařízení nebo Tapaya požadavek odmítly.                                 |

### Chyby [#errors]

| Kód           | Důvod                  | Popis                                          |
| ------------- | ---------------------- | ---------------------------------------------- |
| `403` / `404` | Objekt není dostupný   | Nemáte oprávnění nebo objekt neexistuje.       |
| `409`         | `device_busy`          | Zařízení již má aktivní požadavek nebo platbu. |
| `409`         | `device_unavailable`   | Zařízení je připojeno, ale není připraveno.    |
| `409`         | `idempotency_conflict` | Klíč již byl použit s jiným tělem.             |
| `503`         | `device_offline`       | Zařízení není připojeno.                       |

Chyby validace částky a měny se řídí stejnými konvencemi jako zbytek
[Platform API](/platform-api).

### Události webhooků [#webhook-events]

| Událost                       | Odesílá se, když                                                            | Obsah `data`                                                                                                         |
| ----------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `device.connected`            | Zařízení naváže nové aktivní spojení.                                       | `externalDeviceId`, `connected`, `availability`, `lastSeenAt`                                                        |
| `device.disconnected`         | Spojení se uzavře nebo vyprší časový limit kontroly aktivity spojení.       | `externalDeviceId`, `connected`, `availability`, `lastSeenAt`                                                        |
| `device.availability_changed` | Dostupnost se změní mezi `ready`, `busy` a `unavailable`.                   | `externalDeviceId`, `availability`, volitelně `reason`                                                               |
| `payment_request.updated`     | Požadavek je uložen, potvrzen, odmítnut, vyprší nebo k němu vznikne platba. | `requestId`, `externalDeviceId`, `status`, `paymentToken`, `reference`, `reason`, `expiresAt`                        |
| `payment.updated`             | Změní se stav platby.                                                       | `paymentToken`, `requestId`, `externalDeviceId`, `status`, `reference`, `amount`, `currency`, volitelně `receiptUrl` |

Každá událost přichází jako HTTP `POST` ve stejné obálce:

```json
{
    "eventId": "...",
    "type": "payment_request.updated",
    "occurredAt": "2026-01-13T16:30:59Z",
    "data": {
        "requestId": "...",
        "externalDeviceId": "till-01",
        "status": "acknowledged",
        "paymentToken": null,
        "reference": "ORDER-1042",
        "reason": null,
        "expiresAt": "2026-01-13T16:30:59Z"
    }
}
```
