feat(merchants): add raw data import (csv) for merchants (#1992)

* feat(merchants): add csv import endpoint for merchants

* docs: update endpoint docs

* fix(merchant): recommended ai fixes
This commit is contained in:
Blaž Dular
2026-06-06 16:33:32 +02:00
committed by GitHub
parent d88d6e9e58
commit 94422955f8
18 changed files with 512 additions and 37 deletions

View File

@@ -1,6 +1,6 @@
# Merchants API
The Merchants API allows external applications to retrieve merchants within Sure. Merchants represent payees or vendors associated with transactions.
The Merchants API allows external applications to retrieve and bulk-import merchants within Sure. Merchants represent payees or vendors associated with transactions.
## Generated OpenAPI specification
@@ -21,7 +21,10 @@ The Merchants API allows external applications to retrieve merchants within Sure
## Authentication requirements
All merchant endpoints require an OAuth2 access token or API key that grants the `read` scope.
| Endpoint | Required scope |
| --- | --- |
| `GET` endpoints | `read` |
| `POST /api/v1/merchants` (CSV import) | `write` |
## Available endpoints
@@ -29,6 +32,7 @@ All merchant endpoints require an OAuth2 access token or API key that grants the
| --- | --- | --- |
| `GET /api/v1/merchants` | `read` | List all merchants available to the family. |
| `GET /api/v1/merchants/{id}` | `read` | Retrieve a single merchant by ID. |
| `POST /api/v1/merchants` | `write` | Bulk-import merchants from a CSV file. |
Refer to the generated [`openapi.yaml`](openapi.yaml) for request/response schemas, reusable components, and security definitions.
@@ -104,6 +108,84 @@ When creating or updating transactions, you can assign a merchant using the `mer
}
```
## Importing merchants via CSV
`POST /api/v1/merchants` accepts a `multipart/form-data` upload and bulk-creates `FamilyMerchant` records. Existing merchants with the same name are skipped (no update, no error).
### Request
```http
POST /api/v1/merchants
Content-Type: multipart/form-data
X-Api-Key: <write-scoped-key>
file=@merchants.csv
```
### CSV format
| Column | Required | Description |
| --- | --- | --- |
| `name` | Yes | Merchant name. Rows with a blank name are skipped. |
| `color` | No | Hex colour code (e.g. `#e99537`). Defaults to a random palette colour. |
| `website_url` | No | Merchant website. Aliases accepted: `website url`, `website`. |
The header row is required. Column names are matched case-insensitively and extra spaces, underscores, and asterisks are ignored (e.g. `Name*`, `Website URL`, and `website_url` all match).
Example CSV:
```csv
name,color,website_url
Coffee Shop,#e99537,https://coffeeshop.com
Pizza Palace,#4da568,https://pizzapalace.com
Bookstore,,
```
### Response — 201 Created
```json
{
"imported": 2,
"skipped": 1,
"merchants": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Coffee Shop",
"type": "FamilyMerchant",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "Pizza Palace",
"type": "FamilyMerchant",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
]
}
```
`skipped` counts rows where a merchant with that name already exists for the family.
### Error responses for CSV import
| HTTP status | `error` value | Cause |
| --- | --- | --- |
| `401` | `unauthorized` | Missing or invalid API key. |
| `403` | `forbidden` | API key lacks the `write` scope. |
| `422` | `missing_file` | No `file` parameter supplied. |
| `422` | `file_too_large` | File exceeds 10 MB. |
| `422` | `invalid_file_type` | File is not a recognised CSV MIME type. |
| `422` | `missing_column` | CSV has no `name` column. |
| `422` | `invalid_csv` | CSV is malformed or cannot be parsed. |
## Importing merchants via the web UI
Merchants can also be imported through the built-in multi-step import flow at **Settings → Imports → New Import → Raw Data → Import merchants**. The flow supports the same CSV format as the API endpoint (upload → configure → clean → publish).
A shortcut button ("Import merchants") is also available directly on the **Merchants** page.
## Error responses
Errors conform to the shared `ErrorResponse` schema in the OpenAPI document: