You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Create a new portfolio by uploading allocations from a CSV or JSON file. This is the primary endpoint consumed by the frontend [`BulkPortfolioImport.tsx`](frontend/src/components/BulkPortfolioImport.tsx) component.
253
+
254
+
```bash
255
+
POST /api/v1/portfolio/import
256
+
Content-Type: application/json | text/csv
257
+
```
258
+
259
+
#### Accepted Formats
260
+
261
+
**JSON — wrapped object:**
262
+
```json
263
+
{
264
+
"allocations": [
265
+
{ "asset": "XLM", "allocation_pct": 40 },
266
+
{ "asset": "USDC", "allocation_pct": 35 },
267
+
{ "asset": "BTC", "allocation_pct": 25 }
268
+
],
269
+
"userAddress": "GALPHABET...",
270
+
"name": "My Portfolio",
271
+
"description": "Optional description"
272
+
}
273
+
```
274
+
275
+
**JSON — bare array:**
276
+
```json
277
+
[
278
+
{ "asset": "XLM", "allocation_pct": 40 },
279
+
{ "asset": "USDC", "allocation_pct": 35 },
280
+
{ "asset": "BTC", "allocation_pct": 25 }
281
+
]
282
+
```
283
+
284
+
> When using a bare array, `userAddress` must be included as a top-level field of the request or authenticated via JWT.
285
+
286
+
**CSV — raw text with required headers:**
287
+
```csv
288
+
asset,allocation_pct
289
+
XLM,40
290
+
USDC,35
291
+
BTC,25
292
+
```
293
+
294
+
> Send with `Content-Type: text/csv`. Headers must include `asset` and `allocation_pct`.
295
+
296
+
#### Request Schema
297
+
298
+
| Field | Type | Required | Notes |
299
+
|-------|------|----------|-------|
300
+
|`allocations`|`AllocationInputRow[]`| Yes | Array of `{ asset, allocation_pct }`. May also be the top-level body for JSON. |
301
+
|`userAddress`|`string`| Yes | Stellar public key. Required in body or via JWT auth. |
302
+
|`name`|`string`| No | Portfolio display name. |
303
+
|`description`|`string`| No | Portfolio description. |
304
+
305
+
Each `AllocationInputRow`:
306
+
307
+
| Field | Type | Constraints |
308
+
|-------|------|-------------|
309
+
|`asset`|`string`| Required, normalized to uppercase. Must exist in the asset registry, be enabled, and not quarantined. |
310
+
|`allocation_pct`|`number`| Required, finite, `0–100`. Duplicate assets are merged by summing percentages. |
311
+
312
+
#### Validation Rules
313
+
314
+
- Allocations must sum to **100%** (tolerance: 0.01%).
315
+
- Maximum **10 distinct assets**.
316
+
- Maximum **5 000 rows**.
317
+
- `allocation_pct` must be a finite number between 0 and 100 inclusive.
318
+
- Asset codes are validated against the internal asset registry; unknown, disabled, or quarantined assets are rejected.
319
+
- Duplicate asset rows are merged (percentages summed) before validation.
320
+
321
+
#### Sample cURL — JSON
322
+
323
+
```bash
324
+
curl -X POST http://localhost:3001/api/v1/portfolio/import \
325
+
-H "Content-Type: application/json" \
326
+
-d '{
327
+
"allocations": [
328
+
{ "asset": "XLM", "allocation_pct": 60 },
329
+
{ "asset": "USDC", "allocation_pct": 40 }
330
+
],
331
+
"userAddress": "GALPHABET...",
332
+
"name": "Imported Portfolio"
333
+
}'
334
+
```
335
+
336
+
#### Sample cURL — CSV
337
+
338
+
```bash
339
+
curl -X POST http://localhost:3001/api/v1/portfolio/import \
340
+
-H "Content-Type: text/csv" \
341
+
--data-binary "asset,allocation_pct
342
+
XLM,60
343
+
USDC,40"
344
+
```
345
+
346
+
#### Success Response (201)
347
+
348
+
```json
349
+
{
350
+
"success": true,
351
+
"data": {
352
+
"portfolioId": "portfolio-abc123",
353
+
"status": "created"
354
+
},
355
+
"error": null,
356
+
"timestamp": "2025-01-01T00:00:00.000Z"
357
+
}
358
+
```
359
+
360
+
#### Validation Error Response (400)
361
+
362
+
Returned when the payload fails validation. The `errors` array contains per-row details;`meta` summarises totals.
0 commit comments