> Chytapay is a bank-transfer payments platform in Argentina. This page is part of the Integration API docs: https://chytapay.com.ar/api/
> Recommended reconciliation: by CUIL. Environments: test (https://integration-api.test.chytapay.com.ar) and production (https://integration-api.chytapay.com.ar).
> Page: "Connect merchant accounts" (https://chytapay.com.ar/api/en/conectar-comercios/), updated on 2026-10-09.
> Full index for AI assistants: https://chytapay.com.ar/llms.txt

# Connect merchant accounts

How a merchant account authorizes your integration with OAuth, and how to use, refresh, and revoke its tokens.

By the end, your integration holds the `idToken` of each linked merchant account, to create charges on its behalf, and knows how to refresh it, revoke it and unlink.

The OAuth flow between your backend, the merchant's browser and the Auth API.

1. Your backend redirects to `GET /integration/oauth2/authorize` with a random `state`.
2. The merchant signs in on Chytapay and authorizes.
3. The Auth API returns to your `redirect_uri` with `code` and `state`.
4. Your backend validates the `state`.
5. Your backend sends `code`, `clientId` and `clientSecret` to `POST /integration/oauth2/token`.
6. The Auth API returns the merchant's `idToken` and `refreshToken`.

## Before you begin

All from the same environment, Sandbox or Production:

- The `clientId` and `clientSecret`, in the Portal under [**Credenciales**](https://chytapay.com.ar/portal/credentials) (Credentials). The `clientSecret` goes only from your backend to the Auth API (`/token`, `/refresh` and `/revoke`).
- An `https` `redirect_uri` registered in the Portal under [**Configuración**](https://chytapay.com.ar/portal/settings) (Settings).
- A merchant account: the one that receives the payments, not your integration admin user.

The flow runs against the Auth API, under `/integration/oauth2/`: `https://auth-api.test.chytapay.com.ar` in Sandbox and `https://auth-api.chytapay.com.ar` in Production.



## 1. Send the merchant to authorize



In your backend, generate a random `state`, store it in the session and redirect the browser to `GET /integration/oauth2/authorize`:

| Parameter       | Required              | Value                                  |
| --------------- | --------------------- | -------------------------------------- |
| `response_type` | Yes                   | `code`                                 |
| `client_id`     | Yes                   | Your integration's `clientId`          |
| `redirect_uri`  | Yes                   | A registered `redirect_uri`, identical |
| `state`         | No, but always use it | Random, one per attempt                |
| `scope`         | No                    | Defaults to `openid email profile`     |

The camelCase names (`clientId`, `redirectUri`, `responseType`) are also accepted, but use snake_case in new code.

```js
import crypto from 'node:crypto';

const AUTH_API = 'https://auth-api.test.chytapay.com.ar';
const REDIRECT_URI = 'https://yourapp.com/oauth/callback';

// Express handler: the merchant clicks "Connect with Chytapay".
app.get('/connect-chytapay', (req, res) => {
  const state = crypto.randomBytes(32).toString('base64url');
  req.session.chytapayState = state;

  const url = new URL(`${AUTH_API}/integration/oauth2/authorize`);
  url.search = new URLSearchParams({
    response_type: 'code',
    client_id: process.env.CHYTAPAY_CLIENT_ID,
    redirect_uri: REDIRECT_URI,
    state,
  }).toString();

  res.redirect(url.toString());
});
```

The authorization screen shows your integration's name and logo, and the fee if you have an active offer. **The merchant account signs in there, never the integration admin.** The screen uses `POST /integration/oauth2/complete-login`, which your integration doesn't call.

## 2. Receive the code and validate the state

When the merchant authorizes, Chytapay redirects their browser to your `redirect_uri`:

```
https://yourapp.com/oauth/callback?code=<code>&state=<the state you sent>
```

Validating the `state` stops an attacker from linking your user to someone else's merchant account.

1. Compare the `state` in the query, which Chytapay returns unchanged, with the one in the session. If it's missing or doesn't match, stop the flow.
2. Delete the `state` from the session: it's valid for one attempt.
3. Exchange the `code` for tokens: it lasts **10 minutes** and is **single use**. If it expires, the merchant authorizes again.



## 3. Exchange the code for tokens



In your backend, send `POST /integration/oauth2/token` a JSON body with `grantType: "authorization_code"`, the `code`, your `clientId` and `clientSecret`, and the same `redirectUri` from `/authorize`, identical:

**cURL**

```bash
curl -X POST https://auth-api.test.chytapay.com.ar/integration/oauth2/token \
  -H "Content-Type: application/json" \
  -d '{
    "grantType": "authorization_code",
    "code": "'"$CODE"'",
    "clientId": "'"$CHYTAPAY_CLIENT_ID"'",
    "clientSecret": "'"$CHYTAPAY_CLIENT_SECRET"'",
    "redirectUri": "https://yourapp.com/oauth/callback"
  }'
```

**Response**

```json
{
  "idToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJjdHkiOiJKV1QiLCJlbmMiOiJBMjU2R0NNIn0...",
  "tokenType": "Bearer",
  "expiresIn": 3600
}
```

With this response the merchant is linked. Store its `refreshToken`: it lets you keep operating without a new authorization. If the merchant authorizes again, you get new tokens.

> **Note:** In Sandbox, you can skip OAuth with [a test merchant account](https://chytapay.com.ar/api/en/getting-started/#testing). It doesn't exist in Production.

## 4. Confirm which merchant authorized

With the `idToken` you just obtained, call `GET /me` on the Integration API to find out which merchant account it belongs to, and store its `merchantUserId` next to the link in your system:

**cURL**

```bash
curl https://integration-api.test.chytapay.com.ar/me \
  -H "Authorization: Bearer $ID_TOKEN"
```

**Response**

```json
{
  "merchantUserId": "3f8a1c52-7d4e-4b19-9a60-2c5e8d1f0b47",
  "email": "contacto@papeleria-ejemplo.com.ar",
  "fantasyName": "Papelería del Sur",
  "accountHolderName": "Del Sur Papelería S.R.L.",
  "accountHolderTaxId": "30123456781",
  "integration": { "name": "My integration" },
  "tenantKey": "sede-centro",
  "linkedAt": "2026-05-20T14:30:00.000Z"
}
```

| Field                | What it is                                                                                                                               |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `merchantUserId`     | The merchant account's identifier in Chytapay. It is stable: store it to recognize the account.                                          |
| `email`              | The merchant account's email.                                                                                                            |
| `fantasyName`        | The merchant's trade name.                                                                                                               |
| `accountHolderName`  | The account holder: the legal name for a legal entity, or first and last name for an individual.                                         |
| `accountHolderTaxId` | The holder's CUIT or CUIL, without hyphens.                                                                                              |
| `integration.name`   | Your integration's name, the one the merchant sees when authorizing.                                                                     |
| `tenantKey`          | The key of the [site](https://chytapay.com.ar/api/en/sedes/) the account is linked to. **Absent** if the account isn't linked to a site. |
| `linkedAt`           | The date, in ISO 8601 UTC, of the **first** authorization. If the merchant unlinks and authorizes again, it doesn't change.              |

The identity comes from the `idToken`: `GET /me` takes no parameters and ignores any you send. A merchant account that isn't linked or was unlinked gets **403**.

## Authenticate your API calls

Every Integration API request carries the `idToken` in `Authorization`:

```bash
curl https://integration-api.test.chytapay.com.ar/payment-request/invoice-1001 \
  -H "Authorization: Bearer $ID_TOKEN"
```

Its `aud` claim is your `clientId` and its `sub` is the merchant account: that's how Chytapay knows on whose behalf you operate. `/token` returns no access token: you use the `idToken`. The integration admin token gets **401**, and an unlinked merchant account gets **403**.



| Token          | Lasts                      | How to replace it                  |
| -------------- | -------------------------- | ---------------------------------- |
| `code`         | 10 minutes, single use     | The merchant authorizes again      |
| `idToken`      | 1 hour (`expiresIn: 3600`) | `POST /integration/oauth2/refresh` |
| `refreshToken` | 180 days                   | The merchant authorizes again      |





Before the `idToken` expires, or on a 401, request a new one from `POST /integration/oauth2/refresh` with `grantType: "refresh_token"`, the merchant's stored `refreshToken`, your `clientId` and your `clientSecret`. The merchant isn't involved. The response carries a new `idToken` and no `refreshToken`: the refresh token **doesn't rotate**, keep using the same one until you revoke it or it expires.

## Revoke a refresh token

When you stop operating for a merchant, revoke its refresh token with `POST /integration/oauth2/revoke`:

```bash
curl -X POST https://auth-api.test.chytapay.com.ar/integration/oauth2/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "'"$REFRESH_TOKEN"'",
    "tokenTypeHint": "refresh_token",
    "clientId": "'"$CHYTAPAY_CLIENT_ID"'",
    "clientSecret": "'"$CHYTAPAY_CLIENT_SECRET"'"
  }'
```

It returns 200 with no body. `tokenTypeHint` is optional and its value is `refresh_token`. Revoking **doesn't unlink**: the merchant can authorize again. To end the link, [unlink the merchant](https://chytapay.com.ar/api/en/conectar-comercios/#desvincular).

## Unlink a merchant

In the Portal, under [**Cuentas de comercio**](https://chytapay.com.ar/portal/users) (Merchant accounts), use **Desvincular** (Unlink) on the merchant's row. From then on, its tokens get **403** from the Integration API until the merchant authorizes again. If the account was already unlinked, the Portal tells you it isn't linked.

> **Warning:** **Unlinking doesn't cancel pending charges.** They can still be paid and the money reaches the merchant, but your integration stops seeing them. If you don't want them paid, cancel them first with `POST /payment-request/{referenceId}/cancel`.

## Show your logo on the authorization screen

Without a logo, the merchant sees only your integration's name. Upload it in the Portal under [**Configuración**](https://chytapay.com.ar/portal/settings) (Settings), **Logo** section: the Portal scales it to 512 px on its longest side.

It accepts PNG, JPEG or WEBP up to 5 MB, and rejects SVG. Each upload replaces the previous one.

## The redirect_uri isn't the webhook URL

|              | `redirect_uri`                                | Webhook URL                              |
| ------------ | --------------------------------------------- | ---------------------------------------- |
| Purpose      | Receive the `code` when a merchant authorizes | Receive payment events                   |
| Who calls it | The merchant's browser, with a GET            | Chytapay's backend, with a POST          |
| When         | Once per authorization                        | Every time something happens to a charge |

Configure the webhook URL in [Webhooks](https://chytapay.com.ar/api/en/webhooks/#configurar-urls).

## Errors

Errors carry `code`, `message` and `id`, which you use to trace the case. Other API errors are in [Operations and errors](https://chytapay.com.ar/api/en/operacion/).

| Where                           | HTTP | `code`                    | Cause                                                                                                                                                    | What to do                                                                                              |
| ------------------------------- | ---- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `/authorize`                    | 400  | `InvalidRequestError`     | `client_id`, `redirect_uri` or `response_type` missing, or the `redirect_uri` isn't identical (there's no `redirect_uri_mismatch`).                      | Compare protocol, domain, path and trailing slash.                                                      |
| `/authorize`                    | 401  | `InvalidClientError`      | `client_id` from another environment.                                                                                                                    | Use the one in [**Credenciales**](https://chytapay.com.ar/portal/credentials) for the same environment. |
| Authorization screen            | 400  | `InvalidGrantError`       | "No encontramos una cuenta con ese email." ("We couldn't find an account with that email."): the admin or an account from another environment signed in. | Have the merchant account authorize.                                                                    |
| Authorization screen            | 401  | `InvalidCredentialsError` | Wrong password.                                                                                                                                          | Try again.                                                                                              |
| `/token`                        | 400  | `InvalidGrantError`       | `code` expired, used or from another `clientId`, or a different `redirectUri`.                                                                           | Authorize again with the same `redirect_uri`.                                                           |
| `/token`, `/refresh`, `/revoke` | 401  | `InvalidClientError`      | Wrong `clientId` or `clientSecret`.                                                                                                                      | Check [**Credenciales**](https://chytapay.com.ar/portal/credentials).                                   |
| `/token`                        | 500  | `IntegrationSetupError`   | The link wasn't completed; the `code` is already consumed.                                                                                               | Restart from `/authorize`.                                                                              |
| `/refresh`                      | 400  | `InvalidGrantError`       | Refresh token revoked or invalid.                                                                                                                        | Authorize again.                                                                                        |
| Integration API                 | 401  |                           | Token missing, expired or not a merchant account's.                                                                                                      | Refresh the `idToken`.                                                                                  |
| Integration API                 | 403  |                           | Merchant account not linked or unlinked.                                                                                                                 | Authorize.                                                                                              |
| `GET /me`                       | 403  |                           | The merchant account isn't linked to your integration. The body is plain text, not JSON.                                                                 | Authorize.                                                                                              |

- [Create charges](https://chytapay.com.ar/api/en/crear-cobros/): Charges on the merchant's behalf.
- [Webhooks](https://chytapay.com.ar/api/en/webhooks/): Webhook URL and signature.
- [How it works](https://chytapay.com.ar/api/en/concepts/): Actors and linking.
- [Operations and errors](https://chytapay.com.ar/api/en/operacion/): Limits and errors by endpoint.
