API Reference
API Reference
Project: Drop Version: 0.1.0 Date: 2026-02-23 Author: Platform Architect (AI) Status: In Review Reviewers: Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | 2026-02-23 | Platform Architect (AI) | Compiled from source code analysis |
Overview
Drop uses Next.js App Router API routes (app/api/). The application uses a PSD2 pass-through model — Drop never holds customer money. All funds remain in users' bank accounts; Drop uses AISP to read balances and PISP to initiate payments.
Base URL
| Environment | Base URL |
|---|---|
| Production | https://9ef3szvvsb.eu-west-1.awsapprunner.com (future: https://getdrop.no) |
| Staging | https://drop-staging.fly.dev |
| Local | http://localhost:3000 |
Response Envelope
// Success
{ "data": { ... } }
// Success (list)
{ "data": [...], "pagination": { "page": 1, "limit": 20, "total": 100 } }
// Error
{ "error": "error_code", "message": "Human-readable message", "details": [...] }
Authentication
Authentication is via httpOnly cookie (drop_token) containing a signed JWT (HS256, 24h expiry). BankID OIDC is the sole login method.
Mobile clients use Authorization: Bearer <token> (7d expiry, stored in AsyncStorage).
Rate Limits
| Endpoint Group | Limit |
|---|---|
| BankID initiate/callback | 10/min per IP |
| Transaction creation (remittance, qr-payment) | 10/min per IP |
| Exchange rates | 120/min per IP |
| All other endpoints | No IP-level limit (auth required) |
Authentication Endpoints
GET /api/auth/bankid
Initiate BankID OIDC login flow.
| Property | Value |
|---|---|
| Auth | None |
| Rate Limit | 10/min per IP |
Sets bankid_state httpOnly cookie. Returns { "redirectUrl": "https://bankid.no/authorize?..." }.
GET /api/auth/bankid/callback
BankID OIDC callback — creates/finds user, issues session cookie.
| Property | Value |
|---|---|
| Auth | None (validates state cookie) |
| Rate Limit | 10/min per IP |
Query params: code, state
Success: Redirects to /dashboard, sets drop_token cookie. Creates user with kyc_status=approved on first login. Verifies age >= 18 from BankID pid.
GET /api/auth/me
Get current authenticated user with linked bank accounts.
| Property | Value |
|---|---|
| Auth | Required (cookie) |
Response (200):
{
"data": {
"id": "usr_a1b2c3",
"email": "[email protected]",
"firstName": "Amir",
"lastName": "Bašić",
"totalBalance": 58030.0,
"bankAccounts": [
{ "id": "ba_1", "bankName": "DNB", "accountNumber": "1234.56.78901",
"balance": 45230.0, "currency": "NOK", "isPrimary": true }
],
"kycStatus": "approved",
"createdAt": "2026-01-01T00:00:00.000Z"
}
}
Note: balance values are AISP-read from user's real bank — NOT Drop-held funds.
POST /api/auth/logout
Logout — revoke all sessions.
| Property | Value |
|---|---|
| Auth | Required (cookie) |
Response (200): { "message": "Logged out" }
POST /api/auth/refresh
Refresh JWT token.
| Property | Value |
|---|---|
| Auth | Required (cookie) |
Response (200): { "data": { "userId": "usr_...", "email": "...", "role": "user" } }
Deprecated Endpoints (410 Gone)
| Endpoint | Replacement |
|---|---|
POST /api/auth/login |
BankID OIDC |
POST /api/auth/register |
Auto via BankID |
POST /api/auth/verify-otp |
BankID replaces OTP |
Transaction Endpoints
GET /api/transactions
List user transactions with pagination and filtering.
| Property | Value |
|---|---|
| Auth | Required |
Query Parameters:
| Param | Type | Default | Notes |
|---|---|---|---|
page |
int | 1 | Min 1 |
limit |
int | 20 | Min 1, Max 50 |
type |
string | — | remittance or qr_payment |
status |
string | — | processing, completed, or failed |
Response (200):
{
"data": [
{ "id": "tx_rem_1", "type": "remittance", "status": "completed",
"amount": -2000, "currency": "NOK", "recipientName": "Mama Jasmina", "createdAt": "..." }
],
"pagination": { "page": 1, "limit": 20, "total": 3 }
}
GET /api/transactions/[id]
Get single transaction details.
| Property | Value |
|---|---|
| Auth | Required |
Response (200):
{
"data": {
"id": "tx_rem_1", "type": "remittance", "status": "completed",
"sendAmount": 2000, "sendCurrency": "NOK",
"receiveAmount": 23400, "receiveCurrency": "RSD",
"exchangeRate": 11.7, "fee": 10, "total": 2010,
"recipientName": "Mama Jasmina", "recipientCountry": "Serbia",
"createdAt": "...", "completedAt": "..."
}
}
GET /api/transactions/summary
Get all-time + this-month statistics.
| Property | Value |
|---|---|
| Auth | Required |
Response (200): { "data": { "allTime": { totalCount, totalSent, totalPaid, remittanceCount, qrPaymentCount }, "thisMonth": { ... } } }
POST /api/transactions/remittance
Create international money transfer.
| Property | Value |
|---|---|
| Auth | Required |
| Rate Limit | 10/min per IP |
| KYC | kyc_status must be approved |
Request Body:
| Field | Type | Required | Validation |
|---|---|---|---|
recipientId |
string | Yes | Must belong to current user |
amount |
number | Yes | 100–50,000 NOK, max 2 decimal places |
currency |
string | No | Defaults to NOK |
bankAccountId |
string | No | Defaults to primary bank account |
Business logic: Fee = 0.5% of amount. Atomic: debit bank account + create transaction (status: processing).
Response (201): Full transaction object with exchange rate, fee, ETA.
Errors:
| Status | Code | Condition |
|---|---|---|
| 400 | bad_request |
Missing/invalid fields |
| 400 | no_bank_account |
No linked bank account |
| 402 | insufficient_balance |
Balance too low |
| 403 | kyc_required |
KYC not approved |
| 404 | not_found |
Recipient not found |
| 422 | validation_error |
Unsupported currency corridor |
POST /api/transactions/qr-payment
Create QR payment to a merchant.
| Property | Value |
|---|---|
| Auth | Required |
| Rate Limit | 10/min per IP |
Request Body:
| Field | Type | Required | Validation |
|---|---|---|---|
merchantId |
string | Yes | Must exist |
amount |
number | Yes | 1–100,000 NOK, max 2 decimal places |
Business logic: Fee = 1% of amount. Instant (status: completed).
POST /api/transactions/disclosure
Get fee + exchange rate disclosure before payment (Finansavtaleloven compliance).
| Property | Value |
|---|---|
| Auth | Required |
Request Body: { type, amount, currency?, recipientId? }
Response (200):
{
"amount": 2000, "fee": 10, "feePercentage": 0.5,
"exchangeRate": 10.17, "receiveAmount": 20340, "receiveCurrency": "RSD",
"estimatedDelivery": "1-2 business days", "totalCost": 2010
}
GET /api/transactions/[id]/receipt
Get transaction receipt.
| Property | Value |
|---|---|
| Auth | Required |
Returns full receipt with amounts, fees, exchange rate, recipient info. 404 if not owned by user.
Recipient Endpoints
GET /api/recipients
List saved recipients. Bank account numbers masked (*****5678).
| Property | Value |
|---|---|
| Auth | Required |
Query Params: page, limit (max 50). Supported countries: RS, BA, PL, PK, TR.
POST /api/recipients
Add a recipient.
| Property | Value |
|---|---|
| Auth | Required |
Request Body: { name, country, currency, bankAccount, bankName? }
DELETE /api/recipients/[id]
Delete a recipient. Response (204). 404 if not found/owned.
| Property | Value |
|---|---|
| Auth | Required |
Exchange Rate Endpoints
GET /api/rates
Get all NOK exchange rates (public).
| Property | Value |
|---|---|
| Auth | None |
| Rate Limit | 120/min per IP |
Response (200):
{
"data": {
"baseCurrency": "NOK",
"rates": { "RSD": 11.7, "BAM": 1.04, "PLN": 0.41, "PKR": 26.8, "TRY": 3.45, "EUR": 0.089 },
"updatedAt": "..."
}
}
GET /api/rates/[currency]
Get rate for specific currency from NOK. Includes fee: 0.005 (0.5% remittance fee).
| Property | Value |
|---|---|
| Auth | None |
| Rate Limit | 120/min per IP |
Merchant Endpoints
POST /api/merchants/register
Register as merchant (upgrades role to merchant).
| Property | Value |
|---|---|
| Auth | Required |
Request Body:
| Field | Type | Required | Validation |
|---|---|---|---|
businessName |
string | Yes | validateName() |
orgNumber |
string | Yes | Exactly 9 digits, unique |
address |
string | No | Sanitized to 300 chars |
bankAccount |
string | Yes | Payout account |
Returns QR code URI: drop://pay/{merchantId}
GET /api/merchants/dashboard
Merchant revenue stats.
| Property | Value |
|---|---|
| Auth | Required (merchant role) |
Query Params: period = today (default) / week / month
Returns: { revenue, transactionCount, fees, netRevenue, nextPayout, payoutTime }
GET /api/merchants/qr
Get merchant QR code data.
| Property | Value |
|---|---|
| Auth | Required (merchant role) |
Returns: { merchantId, businessName, qrValue: "drop://pay/{id}", address }
GET /api/merchants/transactions
List merchant's QR payment transactions. Customer names partially anonymized.
| Property | Value |
|---|---|
| Auth | Required (merchant role) |
Notification Endpoints
GET /api/notifications
| Property | Value |
|---|---|
| Auth | Required |
| Feature Flag | notifications (default: enabled) |
PATCH /api/notifications
Mark notifications as read. Max 100 IDs per request.
| Property | Value |
|---|---|
| Auth | Required |
| Feature Flag | notifications |
Request Body: { "notificationIds": ["noti_..."] }
Settings Endpoints
GET /api/settings
Get user settings (auto-creates defaults: currency=NOK, language=nb).
| Property | Value |
|---|---|
| Auth | Required |
PATCH /api/settings
Update user settings.
| Property | Value |
|---|---|
| Auth | Required |
Request Body (all optional): { currency?, language?, pushEnabled?, emailEnabled? }
Currency whitelist: EUR, USD, GBP, BAM, CHF, PLN, NOK, RSD, TRY, PKR Language whitelist: nb, en, bs, sq
GDPR & Compliance Endpoints
GET /api/user/data-export
Export all user data (GDPR Art. 20 — right to portability).
| Property | Value |
|---|---|
| Auth | Required |
Returns full export: user profile, transactions, recipients, bank accounts, settings, consents.
DELETE /api/user/account
Request account deletion (GDPR Art. 17 — right to erasure). Data retained 5 years per hvitvaskingsloven.
| Property | Value |
|---|---|
| Auth | Required |
Response (200): { "message": "Account scheduled for deletion", "retentionNote": "Data retained for 5 years per AML requirements" }
GET /api/consents
List GDPR consents.
| Property | Value |
|---|---|
| Auth | Required |
POST /api/consents
Grant or withdraw consent.
| Property | Value |
|---|---|
| Auth | Required |
Request Body:
| Field | Type | Validation |
|---|---|---|
consentType |
string | terms, privacy, marketing, cookies_analytics, cookies_marketing |
granted |
boolean | true to grant, false to withdraw |
Records IP address with consent action.
GET /api/complaints
List user's complaints.
| Property | Value |
|---|---|
| Auth | Required |
Query Params: page, limit (max 100)
POST /api/complaints
Submit a complaint (Finansavtaleloven §3-53 — 15 business day response SLA).
| Property | Value |
|---|---|
| Auth | Required |
Request Body:
| Field | Type | Validation |
|---|---|---|
category |
string | transaction, service, fees, privacy, technical, other |
subject |
string | Max 200 chars |
description |
string | Max 2000 chars |
Cards Endpoints (FUTURE — Feature-Flagged, All Disabled)
All card endpoints are behind feature flags defaulting to false. Requires card issuing partner before activation.
| Endpoint | Feature Flag |
|---|---|
GET /api/cards |
virtualCards |
POST /api/cards |
virtualCards |
GET /api/cards/[id] |
cardDetails |
PATCH /api/cards/[id] |
cardFreeze |
DELETE /api/cards/[id] |
virtualCards |
POST /api/cards/[id]/physical |
physicalCards |
POST /api/cards/[id]/pin |
cardPin |
GET/PUT /api/cards/[id]/limits |
spendingLimits |
Card numbers always masked (---- ---- ---- XXXX). CVV never exposed. PCI-DSS compliant masking.
Health Check
GET /api/health
System health — no auth required.
Response (200):
{
"data": {
"status": "ok",
"version": "0.1.0",
"uptime": 3600,
"checks": {
"db": { "status": "pass", "latencyMs": 2, "driver": "pg" },
"services": { "mode": "production" }
},
"timestamp": "2026-02-23T12:00:00.000Z"
}
}
Response (503 — DB unreachable): { "data": { "status": "down", "checks": { "db": { "status": "fail" } } } }
Related Documents
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | Platform Architect (AI) | 2026-02-23 | |
| Reviewer | |||
| Approver | Alem Bašić |