# Cancellations

> API reference · Wathiqaty Insurer API v1 (draft)

A customer's cancellation request waits for your decision (`cancellation.requested`). The refund is quoted when the request is made, from the scale in force that day, and frozen on the request.

Base URL: `https://sandbox-api.wathiqaty.com/api/v1` (sandbox), `https://api.wathiqaty.com/api/v1` (production).

- List cancellations: `GET /partner/cancellations`
- Get a cancellation: `GET /partner/cancellations/{id}`
- Approve a cancellation: `POST /partner/cancellations/{id}/approve`
- Reject a cancellation: `POST /partner/cancellations/{id}/reject`
- Record a manual refund: `POST /partner/cancellations/{id}/manual-refund`

## List cancellations

`GET /partner/cancellations`

- Auth: Bearer token, scope `cancellations:read`

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `status` | string | no | Cancellation status. One of: `requested`, `refund_pending`, `completed`, `rejected`, `withdrawn`. |
| `policyId` | uuid | no | On this policy. |
| `updatedSince` | date-time | no | Only rows changed at or after this instant. Pass the `updatedAt` of the last row you processed (change feed). |
| `page` | integer | no | Page number, from 1. Default 1. |
| `size` | integer | no | Items per page, 1–100. Default 20. |
| `sortOrder` | string | no | Default `desc`. Use `asc` with `sortBy=updatedAt` for a change feed. One of: `asc`, `desc`. |

### Request examples

```bash
curl -X GET "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.get(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations",
    headers={
        "Authorization": f"Bearer {access_token}",
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Get,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

A page of cancellations.

```json
{
  "status": "success",
  "data": {
    "items": [
      {
        "id": "b7d4e1a9-2c3f-4a58-9e6b-0d1c2b3a4f5e",
        "policyId": "6f1c2d3e-4b5a-4c69-8d7e-1f2a3b4c5d6e",
        "status": "requested",
        "initiatedBy": "customer",
        "reason": "Moved to my employer's group cover",
        "requestedAt": "2026-11-20T11:00:00.000Z",
        "refundQuote": {
          "daysElapsed": 15,
          "tier": {
            "fromDay": 15,
            "toDay": 365,
            "refundPercent": "80.00",
            "basis": "pro_rata"
          },
          "refundAmount": "6202.74",
          "taxesRetained": "1166.67",
          "commissionReversal": "620.27"
        },
        "decidedAt": null,
        "completedAt": null,
        "updatedAt": "2026-11-20T11:00:00.000Z"
      }
    ],
    "meta": {
      "page": 1,
      "size": 20,
      "totalItems": 1,
      "totalPages": 1,
      "hasNextPage": false,
      "hasPreviousPage": false
    }
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "GET",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

## Get a cancellation

`GET /partner/cancellations/{id}`

- Auth: Bearer token, scope `cancellations:read`

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The cancellation's Wathiqaty id. |

### Request examples

```bash
curl -X GET "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests

res = requests.get(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}",
    headers={
        "Authorization": f"Bearer {access_token}",
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Get,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

The cancellation with its frozen refund quote.

```json
{
  "status": "success",
  "data": {
    "id": "b7d4e1a9-2c3f-4a58-9e6b-0d1c2b3a4f5e",
    "policyId": "6f1c2d3e-4b5a-4c69-8d7e-1f2a3b4c5d6e",
    "status": "requested",
    "initiatedBy": "customer",
    "reason": "Moved to my employer's group cover",
    "requestedAt": "2026-11-20T11:00:00.000Z",
    "refundQuote": {
      "daysElapsed": 15,
      "tier": {
        "fromDay": 15,
        "toDay": 365,
        "refundPercent": "80.00",
        "basis": "pro_rata"
      },
      "refundAmount": "6202.74",
      "taxesRetained": "1166.67",
      "commissionReversal": "620.27"
    },
    "decidedAt": null,
    "completedAt": null,
    "updatedAt": "2026-11-20T11:00:00.000Z"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "GET",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Cancellation doesn't exist or isn't your company's. |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## Approve a cancellation

`POST /partner/cancellations/{id}/approve`

- Auth: Bearer token, scope `cancellations:write`
- `Idempotency-Key`: optional

`requested` → `refund_pending` (the refund is then sent through the gateway or recorded manually) or `completed` when nothing is refunded. The policy is cancelled when the cancellation completes.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The cancellation's Wathiqaty id. |

### Request examples

```bash
curl -X POST "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/approve" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)"
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/approve", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Idempotency-Key": crypto.randomUUID(),
  },
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests, uuid

res = requests.post(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/approve",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Post,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/approve");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
req.Headers.TryAddWithoutValidation("Idempotency-Key", Guid.NewGuid().ToString());
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

The cancellation.

```json
{
  "status": "success",
  "data": {
    "id": "b7d4e1a9-2c3f-4a58-9e6b-0d1c2b3a4f5e",
    "policyId": "6f1c2d3e-4b5a-4c69-8d7e-1f2a3b4c5d6e",
    "status": "refund_pending",
    "initiatedBy": "customer",
    "reason": "Moved to my employer's group cover",
    "requestedAt": "2026-11-20T11:00:00.000Z",
    "refundQuote": {
      "daysElapsed": 15,
      "tier": {
        "fromDay": 15,
        "toDay": 365,
        "refundPercent": "80.00",
        "basis": "pro_rata"
      },
      "refundAmount": "6202.74",
      "taxesRetained": "1166.67",
      "commissionReversal": "620.27"
    },
    "decidedAt": null,
    "completedAt": null,
    "updatedAt": "2026-11-20T11:00:00.000Z"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "POST",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Cancellation doesn't exist or isn't your company's. |
| 400 | `bad_request` | Not allowed in the current status (`details.from`, `details.to`). |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## Reject a cancellation

`POST /partner/cancellations/{id}/reject`

- Auth: Bearer token, scope `cancellations:write`
- `Idempotency-Key`: optional

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The cancellation's Wathiqaty id. |

### Body (JSON)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `reason` | string | yes | 3–2000 characters, shown to the customer. |

### Request examples

```bash
curl -X POST "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/reject" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reason":"The policy is past the cancellation window in the wording."}'
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/reject", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    "reason": "The policy is past the cancellation window in the wording."
  }),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests, uuid

res = requests.post(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/reject",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
      "reason": "The policy is past the cancellation window in the wording."
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Post,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/reject");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
req.Headers.TryAddWithoutValidation("Idempotency-Key", Guid.NewGuid().ToString());
req.Content = new StringContent(
    """
    {
      "reason": "The policy is past the cancellation window in the wording."
    }
    """,
    Encoding.UTF8, "application/json");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

The cancellation.

```json
{
  "status": "success",
  "data": {
    "id": "b7d4e1a9-2c3f-4a58-9e6b-0d1c2b3a4f5e",
    "policyId": "6f1c2d3e-4b5a-4c69-8d7e-1f2a3b4c5d6e",
    "status": "rejected",
    "initiatedBy": "customer",
    "reason": "Moved to my employer's group cover",
    "requestedAt": "2026-11-20T11:00:00.000Z",
    "refundQuote": {
      "daysElapsed": 15,
      "tier": {
        "fromDay": 15,
        "toDay": 365,
        "refundPercent": "80.00",
        "basis": "pro_rata"
      },
      "refundAmount": "6202.74",
      "taxesRetained": "1166.67",
      "commissionReversal": "620.27"
    },
    "decidedAt": null,
    "completedAt": null,
    "updatedAt": "2026-11-20T11:00:00.000Z"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "POST",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Cancellation doesn't exist or isn't your company's. |
| 400 | `bad_request` | Not allowed in the current status (`details.from`, `details.to`). |

Plus the generic errors any route can return: [Errors](/guides/errors.md).

## Record a manual refund

`POST /partner/cancellations/{id}/manual-refund`

- Auth: Bearer token, scope `cancellations:write`
- `Idempotency-Key`: required

You refunded the customer outside the payment gateway: `refund_pending` → `completed`.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | uuid | yes | The cancellation's Wathiqaty id. |

### Body (JSON)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `reference` | string | yes | Your transfer reference, 1–255 characters. |
| `paidAt` | date-time | yes | When the refund was paid. |

### Request examples

```bash
curl -X POST "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/manual-refund" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reference":"TRX-88213","paidAt":"2026-11-22T10:00:00Z"}'
```

```js
const res = await fetch("https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/manual-refund", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${accessToken}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    "reference": "TRX-88213",
    "paidAt": "2026-11-22T10:00:00Z"
  }),
})
const { data, error } = await res.json()
if (!res.ok) throw new Error(`${res.status} ${error.code}`)
```

```python
import requests, uuid

res = requests.post(
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/manual-refund",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
      "reference": "TRX-88213",
      "paidAt": "2026-11-22T10:00:00Z"
    },
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {payload['error']['code']}")
data = payload["data"]
```

```csharp
using var req = new HttpRequestMessage(HttpMethod.Post,
    "https://sandbox-api.wathiqaty.com/api/v1/partner/cancellations/{id}/manual-refund");
req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
req.Headers.TryAddWithoutValidation("Idempotency-Key", Guid.NewGuid().ToString());
req.Content = new StringContent(
    """
    {
      "reference": "TRX-88213",
      "paidAt": "2026-11-22T10:00:00Z"
    }
    """,
    Encoding.UTF8, "application/json");
using var res = await http.SendAsync(req);
var json = await res.Content.ReadAsStringAsync();
res.EnsureSuccessStatusCode();
```

### Response 200

The cancellation, now `completed`.

```json
{
  "status": "success",
  "data": {
    "id": "b7d4e1a9-2c3f-4a58-9e6b-0d1c2b3a4f5e",
    "policyId": "6f1c2d3e-4b5a-4c69-8d7e-1f2a3b4c5d6e",
    "status": "completed",
    "initiatedBy": "customer",
    "reason": "Moved to my employer's group cover",
    "requestedAt": "2026-11-20T11:00:00.000Z",
    "refundQuote": {
      "daysElapsed": 15,
      "tier": {
        "fromDay": 15,
        "toDay": 365,
        "refundPercent": "80.00",
        "basis": "pro_rata"
      },
      "refundAmount": "6202.74",
      "taxesRetained": "1166.67",
      "commissionReversal": "620.27"
    },
    "decidedAt": null,
    "completedAt": null,
    "updatedAt": "2026-11-20T11:00:00.000Z"
  },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/…",
    "method": "POST",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

### Errors

| Status | Code | When |
| --- | --- | --- |
| 404 | `not_found` | Cancellation doesn't exist or isn't your company's. |
| 400 | `bad_request` | Not allowed in the current status (`details.from`, `details.to`). |

Plus the generic errors any route can return: [Errors](/guides/errors.md).
