# Webservices API

## Overview

The Webservices API is for server-to-server integrations over HTTPS: retrieve transaction data, refund transactions, and receive transaction events via webhooks. Choose it for reporting, reconciliation and refund workflows, independently of how the payments were made.

## At a glance

|  |  |
| --- | --- |
| Base URL | Production: https://api.yavin.com/api/v5. Sandbox: https://api.sandbox.yavin.com/api/v5 |
| Authentication | API key in the Yavin-Secret header |
| Naming convention | Depends on the endpoint: Fetch transactions uses camelCase (startDate, serialNumbers) with two snake_case exceptions (only_pending, exclude_e_commerce); Refund uses snake_case (user_email). Refer to each parameters table |
| Current version | v5 |
| Result delivery | Synchronous for fetch endpoints; refunds are synchronous with a webhook on final acceptance or rejection |

## Endpoints

| Endpoint | Method | Purpose |
| --- | --- | --- |
| /pos/transactions/ | POST | Fetch a batch of transactions |
| /transaction/ | GET | Fetch a single transaction |
| /transaction/{id}/refund/ | POST | Refund a transaction |

## Before you start

Authentication and headers. Every request must carry your company API key in the Yavin-Secret header and declare Content-Type: application/json. You can find your API key on [my.yavin.com](https://my.yavin.com/) in the API tab.

Timezones. On the fetch endpoint, startTime and endTime must be provided without any timezone designator (no trailing Z) and are interpreted in the timezone you provide. Always set the timezone parameter to get correct data.

Transient errors. Occasional 502 responses on heavy calls are transient gateway errors, not application errors: retry with exponential backoff. For historical imports, chunk requests day by day rather than fetching large windows.

Webhook delivery.

- Webhook URL must be HTTPS and must not attempt a redirection

- Yavin expects an HTTP 200 acknowledgement. On failure the webhook is retried at: 1 minute, 10 minutes, 1 hour, 8 hours later. After the retries are exhausted, the webhook is deactivated

For the standardized transaction webhook payload, see [Webhooks Management](https://app.notion.com/p/3bc9a8f4fd9a81b582ace5b88b72adf0).

---

## Endpoints

### Fetch a batch of transactions

`POST https://api.yavin.com/api/v5/pos/transactions/`

Retrieves the transactions of a Yavin account. Use limit and offset for pagination, and the filters (dates, terminals, schemes) to retrieve only what you need. If no date filters are set, the last 30 days are fetched.

Transactions are returned in reverse chronological order (most recent first). When paginating while new transactions arrive, fix endDate and endTime to a point in the past so pages stay consistent (no duplicates, no gaps).

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `startDate` | String | no |  | Earliest date to include, yyyy-MM-dd, inclusive. If no date filters are set, the last 30 days are fetched |
| `endDate` | String | no |  | Latest date to include, yyyy-MM-dd, inclusive (the whole day unless endTime is set) |
| `startTime` | String | no | 00:00:00 | Start time on startDate, HH:mm:ss, no timezone designator, interpreted in timezone |
| `endTime` | String | no | 23:59:59 | End time on endDate, HH:mm:ss, no timezone designator, interpreted in timezone |
| `timezone` | String | no |  | Timezone ID of the caller (eg Europe/Paris). Strongly advised |
| `limit` | Integer | no | 20 | Max transactions returned, up to 200 |
| `offset` | Integer | no | 0 | Pagination offset |
| `serialNumbers` | Array | no |  | Terminal identifiers to filter on (visible in Yavin Services) |
| `references` | Array | no |  | References to filter on |
| `schemes` | Array | no |  | Schemes to filter on |
| `currencyCode` | String | no |  | ISO 4217 code |
| `only_pending` | Boolean | no | false | true: only pending transactions |
| `exclude_e_commerce` | Boolean | no | false | true: exclude ecommerce transactions |

#### Request

_Python (docs)_
```python
url = 'https://api.yavin.com/api/v5/pos/transactions/'
headers = {
  'Content-Type': 'application/json',
  'Yavin-Secret': 'YAVIN_API_KEY'
}
params = {
  "serialNumbers": ["123456789"],
  "timezone": "Europe/Paris",
  "startDate": "2022-01-01",
  "endDate": "2022-02-01",
  "startTime": "00:00:00",
  "endTime": "23:59:59",
  "limit": 100
}
```

_cURL_
```bash
curl -X POST 'https://api.yavin.com/api/v5/pos/transactions/' \
  -H 'Yavin-Secret: YAVIN_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "serialNumbers": [
    "123456789"
  ],
  "timezone": "Europe/Paris",
  "startDate": "2022-01-01",
  "endDate": "2022-02-01",
  "startTime": "00:00:00",
  "endTime": "23:59:59",
  "limit": 100
}'
```

_Node_
```javascript
(async () => {
  const response = await fetch("https://api.yavin.com/api/v5/pos/transactions/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Yavin-Secret": process.env.YAVIN_API_KEY,
    },
    body: JSON.stringify({
      "serialNumbers": [
        "123456789"
      ],
      "timezone": "Europe/Paris",
      "startDate": "2022-01-01",
      "endDate": "2022-02-01",
      "startTime": "00:00:00",
      "endTime": "23:59:59",
      "limit": 100
    }),
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import os
import requests

headers = {
    "Content-Type": "application/json",
    "Yavin-Secret": os.environ["YAVIN_API_KEY"],
}

payload = {
  "serialNumbers": [
    "123456789"
  ],
  "timezone": "Europe/Paris",
  "startDate": "2022-01-01",
  "endDate": "2022-02-01",
  "startTime": "00:00:00",
  "endTime": "23:59:59",
  "limit": 100
}

response = requests.post(
    "https://api.yavin.com/api/v5/pos/transactions/",
    headers=headers,
    json=payload,
)
response.raise_for_status()
data = response.json()
```

#### Response

_JSON_
```json
{
  "total": 356,
  "limit": 100,
  "offset": 0,
  "count": 100,
  "transactions": [
    {
      "amount": 100,
      "createdAt": "2022-05-10T14:09:06",
      "customer": { "email": "", "phone": "0612345687" },
      "giftAmount": 0,
      "issuer": "BNP",
      "scheme": "CB",
      "status": "ok",
      "transactionId": "IEqoLmjuRqfq",
      "type": "debit",
      "serialNumber": "123456789"
    }
  ]
}
```

### Fetch a transaction

`GET https://api.yavin.com/api/v5/transaction/`

Retrieves the details of a single transaction. The transaction must belong to the company associated with the API key; otherwise an error is returned.

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `transactionId` | String | yes |  | Unique identifier of the transaction, passed in the query string |

#### Request

_cURL_
```bash
curl -X GET 'https://api.yavin.com/api/v5/transaction/?transactionId=<transactionId>' \
  -H 'Yavin-Secret: YAVIN_API_KEY'
```

_Node_
```javascript
(async () => {
  const response = await fetch("https://api.yavin.com/api/v5/transaction/?transactionId=<transactionId>", {
    method: "GET",
    headers: {
      "Yavin-Secret": process.env.YAVIN_API_KEY,
    },
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import os
import requests

headers = {
    "Yavin-Secret": os.environ["YAVIN_API_KEY"],
}

response = requests.get(
    "https://api.yavin.com/api/v5/transaction/?transactionId=<transactionId>",
    headers=headers,
)
response.raise_for_status()
data = response.json()
```

#### Response

_JSON_
```json
{
  "transactionDetails": {
    "askedAmount": 1000,
    "giftAmount": 0,
    "totalAmount": 1000,
    "serverDatetime": "2022-05-10T14:09:06",
    "deviceDatetime": "2022-05-10T14:09:06",
    "issuer": "BNP",
    "scheme": "CB",
    "status": "ok",
    "transactionId": "mk8wHcHUeQol",
    "type": "debit",
    "serialNumber": "123456789",
    "clientTicket": "ticket_client_123",
    "companyTicket": "ticket_merchant_123",
    "receiptTicket": { "data": "receipt_data", "format": "text" },
    "reference": "server_reference_123"
  }
}
```

### Refund a transaction

`POST https://api.yavin.com/api/v5/transaction/{original_transaction_id}/refund/`

Initiates a refund of a previously processed transaction.

> ⚠️ Refund conditions. The point of sale must have "refunds by API" activated. The original transaction was processed by the same establishment, has no refund associated yet (multi-refunds on the same transaction are not possible), and the refund amount is greater than 0 and equal to or less than the original transaction amount. AMEX, CUP, Cash, ANCV, Conecs, Edenred and Swile transactions are not refundable. Since only one refund per transaction is possible, a partial refund makes the remaining amount permanently non-refundable via API: refund the full intended amount in a single call.

> 💡 If user_email is not known in the Yavin database or has no access to MyYavin BO, an approval email is sent to all admin users of the merchant backoffice, and the refund stays in waiting until approved.

#### Parameters

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `amount` | Integer | yes |  | Refund amount in cents, must be positive |
| `user_email` | String | yes |  | Email of the user initiating the refund |
| `vendor` | Vendor | yes |  | Software editor information |
| `prioritise_tip` | Boolean | no | false | On a partial refund: false (default), the transaction amount is refunded first; true, the tip is refunded first |

#### Request

_JSON_
```json
{
  "amount": 1000,
  "user_email": "user@example.com",
  "vendor": {
    "software_name": "MyPOS",
    "software_version": "1.0"
  }
}
```

_cURL_
```bash
curl -X POST 'https://api.yavin.com/api/v5/transaction/{original_transaction_id}/refund/' \
  -H 'Yavin-Secret: YAVIN_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "amount": 1000,
  "user_email": "user@example.com",
  "vendor": {
    "software_name": "MyPOS",
    "software_version": "1.0"
  }
}'
```

_Node_
```javascript
(async () => {
  const response = await fetch("https://api.yavin.com/api/v5/transaction/{original_transaction_id}/refund/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Yavin-Secret": process.env.YAVIN_API_KEY,
    },
    body: JSON.stringify({
      "amount": 1000,
      "user_email": "user@example.com",
      "vendor": {
        "software_name": "MyPOS",
        "software_version": "1.0"
      }
    }),
  });

  if (!response.ok) throw new Error(`Yavin API error ${response.status}`);
  const data = await response.json();
  console.log(data);
})();
```

_Python_
```python
import os
import requests

headers = {
    "Content-Type": "application/json",
    "Yavin-Secret": os.environ["YAVIN_API_KEY"],
}

payload = {
  "amount": 1000,
  "user_email": "user@example.com",
  "vendor": {
    "software_name": "MyPOS",
    "software_version": "1.0"
  }
}

response = requests.post(
    "https://api.yavin.com/api/v5/transaction/{original_transaction_id}/refund/",
    headers=headers,
    json=payload,
)
response.raise_for_status()
data = response.json()
```

#### Response

All outcomes in one view:

| HTTP code | status | Meaning | Extra fields |
| --- | --- | --- | --- |
| 201 | refunded | The refund was processed | message = ok, transaction_id (refund transaction) |
| 202 | pending | Received, further verifications required before processing | message = ok, transaction_id |
| 202 | waiting | Received, manual approval required in the backoffice | message = ok, transaction_id, approval_url (backoffice URL to review the refund) |
| 400 |  | Request malformed: JSON object keyed by field, eg "amount": ["cannot be greater than the transaction's total amount"] |  |
| 400 |  | Refund not possible: error field, eg This transaction has already been refunded |  |
| 422 | not_possible | Refused by the issuer | message = The refund was refused by the issuer |

A webhook is sent only when the refund has been accepted or rejected, since a pending state is already known from the synchronous response. Its body is the RefundWebhook object documented below.

Set up

- Ecommerce transaction: the webhook_url given at link creation is called back

- Proxi transaction: ask the Yavin Support team to configure a webhook URL for each onboarded company

---

## Other

### The Transaction object

Returned in the list by the batch fetch endpoint.

```json
{
  "amount": 100,
  "giftAmount": 0,
  "createdAt": "2022-05-10T14:09:06",
  "currencyCode": "EUR",
  "status": "ok",
  "transactionId": "IEqoLmjuRqfq",
  "type": "debit",
  "serialNumber": "123456789",
  "scheme": "CB",
  "issuer": "BNP",
  "reference": "Luke",
  "cartId": "ORDER-2026-000123",
  "customer": { "email": "", "phone": "0612345687" }
}
```

#### Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| amount | Integer | Amount in cents |
| giftAmount | Integer | Tip or donation in cents |
| createdAt | String | Creation date, ISO 8601 |
| currencyCode | String | ISO 4217 code |
| status | String | Transaction status |
| transactionId | String | Unique identifier |
| type | String | Transaction type (eg debit) |
| serialNumber | String | Terminal identifier |
| scheme | String | Acceptance network (eg CB) |
| issuer | String | Card issuer |
| reference | String | Waiter or person who performed the transaction |
| cartId | String | Order reference from the POS |
| customer | Customer | Customer details when available |

---

### The TransactionDetails object

Returned by the single fetch endpoint, under transactionDetails. Richer than the Transaction object: it carries the tickets and both timestamps.

```json
{
  "askedAmount": 1000,
  "giftAmount": 0,
  "totalAmount": 1000,
  "serverDatetime": "2022-05-10T14:09:06",
  "deviceDatetime": "2022-05-10T14:09:06",
  "issuer": "BNP",
  "scheme": "CB",
  "status": "ok",
  "transactionId": "mk8wHcHUeQol",
  "type": "debit",
  "serialNumber": "123456789",
  "clientTicket": "ticket_client_123",
  "companyTicket": "ticket_merchant_123",
  "receiptTicket": { "data": "receipt_data", "format": "text" },
  "reference": "server_reference_123"
}
```

#### Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| askedAmount | Integer | Amount requested, in cents |
| giftAmount | Integer | Tip or donation, in cents |
| totalAmount | Integer | Total amount, in cents |
| serverDatetime | String | Creation timestamp on the server, ISO 8601 |
| deviceDatetime | String | Creation timestamp on the device, ISO 8601 |
| issuer | String | Card issuer (eg BNP) |
| scheme | String | Acceptance network (eg CB, VISA) |
| status | String | Transaction status (eg ok) |
| transactionId | String | Unique identifier |
| type | String | debit, credit, ... |
| serialNumber | String | Terminal serial number |
| clientTicket / companyTicket | String | Customer and merchant tickets |
| receiptTicket | Object | Receipt content: data and format |
| reference | String | Reference linked to the transaction (eg waiter) |

---

### The RefundWebhook object

Sent to your webhook when a refund has been accepted or rejected. Carries both the original transaction and the refund transaction, so you can reconcile without a second call.

```json
{
  "original_transaction": {
    "transaction_id": "txn_1234567890",
    "asked_amount": 5000,
    "gift_amount": 500,
    "total_amount": 5500,
    "pan": "411111******1111",
    "date": "2025-01-10T14:32:45Z"
  },
  "refund_transaction": {
    "transaction_id": "txn_refund_0987654321",
    "asked_amount": 5000,
    "gift_amount": 500,
    "total_amount": 5500,
    "pan": "411111******1111",
    "date": "2025-01-11T09:12:03Z"
  },
  "status": "success",
  "request_user_email": "user@example.com",
  "source": "api"
}
```

#### Attributes

| Attribute | Type | Description |
| --- | --- | --- |
| original_transaction | Object | Details of the original transaction: transaction_id, asked_amount, gift_amount, total_amount (cents), pan (masked), date (ISO 8601) |
| refund_transaction | Object | Same fields for the refund transaction |
| status | String | success or rejected |
| request_user_email | String | Email of the user who initiated the refund |
| source | String | api or myyavin |

---

### The Customer object

Customer details attached to a transaction, when available.

```json
{
  "customer": {
    "first_name": "John",
    "last_name": "Doe",
    "email": "john@yavin.com",
    "phone": "+33612345678"
  }
}
```

#### Attributes

| Attribute | Type | Required | Description |
| --- | --- | --- | --- |
| first_name | String | no | First name |
| last_name | String | no | Last name |
| email | String | no | Email |
| phone | String | no | International format starting with + (eg +33612345678) |
| birth_date | String | no | Birthdate |

---

### The Vendor object

Identifies your software on a refund request. Required, and in snake_case on this endpoint.

```json
{
  "vendor": {
    "software_name": "MyPOS",
    "software_version": "1.0"
  }
}
```

#### Attributes

| Attribute | Type | Required | Description |
| --- | --- | --- | --- |
| software_name | String | yes | Name of the POS software |
| software_version | String | yes | Version of the POS software |

---

### Related pages

[Webhooks Management](https://app.notion.com/p/3bc9a8f4fd9a81b582ace5b88b72adf0)
