Skip to main content

API Reference

Drop Backend API Reference

Auto-generated from source code analysis. All file references are relative to src/drop-app/src/.

Overview

Drop uses Next.js App Router API routes (app/api/). All responses use a consistent JSON envelope:

{ "data": { ... } }                    // Success
{ "error": "code", "message": "...", "details": [...] }  // Error

Authentication is via httpOnly cookie (drop_token) containing a signed JWT (HS256, 24h expiry).

Pass-Through Model: Drop uses a PSD2 pass-through model — it NEVER holds customer money. There is no wallet, no balance, no top-up. User funds remain in their bank account at all times. Drop uses:

  • AISP (Account Information Service Provider) — reads bank balance via Open Banking
  • PISP (Payment Initiation Service Provider) — initiates transfers directly from user's bank account

The bank_accounts.balance field stores the last AISP-read balance from the user's real bank (cached for display) — NOT a Drop-held balance.


Authentication

POST /api/auth/register

Create a new user account.

Field Source
File app/api/auth/register/route.ts
Auth None
Rate Limit 10 req/min per IP

Request Body:

Field Type Required Validation
email string Yes RFC-like regex, unique
password string Yes Min 8 chars, must contain letters + digits
firstName string Yes validateName() — 1-100 chars, at least one letter, no HTML/script
lastName string Yes Same as firstName
phone string No International format +XXXXXXXXXXXX (8-15 digits)
dateOfBirth string Yes ISO date string, must be >= 18 years old

Success Response (201):

{
  "data": {
    "id": "usr_...",
    "email": "[email protected]",
    "firstName": "...",
    "lastName": "...",
    "dateOfBirth": "...",
    "kycStatus": "pending",
    "createdAt": "2026-..."
  }
}

Error Responses:

Status Code Condition
400 bad_request Invalid JSON body
409 conflict Email already registered
422 validation_error Field validation failures (returned in details array)
429 rate_limited Too many requests

POST /api/auth/login

Authenticate with email and password.

Field Source
File app/api/auth/login/route.ts
Auth None
Rate Limit 10 req/min per IP

Request Body:

Field Type Required
email string Yes
password string Yes

Success Response (200):

{
  "data": {
    "id": "usr_...",
    "email": "...",
    "firstName": "...",
    "lastName": "...",
    "kycStatus": "approved"
  }
}

Error Responses:

Status Code Condition
400 bad_request Missing email or password
401 unauthorized Invalid credentials
429 rate_limited Too many requests

GET /api/auth/me

Get current authenticated user with bank accounts.

Field Source
File app/api/auth/me/route.ts
Auth Required (cookie)

Success Response (200):

{
  "data": {
    "id": "usr_...",
    "email": "...",
    "firstName": "...",
    "lastName": "...",
    "totalBalance": 58030.0,
    "bankAccounts": [
      {
        "id": "ba_1",
        "bankName": "DNB",
        "accountNumber": "1234.56.78901",
        "balance": 45230.0,
        "currency": "NOK",
        "isPrimary": true
      }
    ],
    "kycStatus": "approved",
    "createdAt": "..."
  }
}

POST /api/auth/logout

Logout and revoke all sessions.

Field Source
File app/api/auth/logout/route.ts
Auth Required (cookie)

Calls revokeAllSessions() to invalidate all session records, then clears the auth cookie.

Success Response (200):

{ "message": "Logged out" }

POST /api/auth/refresh

Refresh the authentication token (issue new JWT, create new session record).

Field Source
File app/api/auth/refresh/route.ts
Auth Required (cookie)

Success Response (200):

{
  "data": {
    "userId": "usr_...",
    "email": "...",
    "role": "user"
  }
}

Transactions

GET /api/transactions

List user's transactions with pagination and filtering.

Field Source
File app/api/transactions/route.ts
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

Success 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 }
}

Note: amount is negated in the response (always shown as outgoing).


GET /api/transactions/[id]

Get single transaction details with exchange rate info.

Field Source
File app/api/transactions/[id]/route.ts
Auth Required

Success 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 transaction summary statistics (all-time and this month).

Field Source
File app/api/transactions/summary/route.ts
Auth Required

Success Response (200):

{
  "data": {
    "allTime": {
      "totalCount": 3,
      "totalSent": 5000,
      "totalPaid": 129,
      "remittanceCount": 2,
      "qrPaymentCount": 1
    },
    "thisMonth": { "..." }
  }
}

POST /api/transactions/remittance

Create a remittance (international money transfer).

Field Source
File app/api/transactions/remittance/route.ts
Auth Required
Rate Limit 10 req/min per IP
KYC Must be approved

Request Body:

Field Type Required Validation
recipientId string Yes Must belong to 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:

  1. Verify recipient belongs to user
  2. Look up exchange rate for recipient's currency
  3. Verify bank account exists and has sufficient balance
  4. Fee: 0.5% of amount
  5. Debit bank account (atomic transaction)
  6. Create transaction record with status processing

Success Response (201):

{
  "data": {
    "id": "tx_rem_...",
    "type": "remittance",
    "status": "processing",
    "sendAmount": 2000,
    "sendCurrency": "NOK",
    "receiveAmount": 23400,
    "receiveCurrency": "RSD",
    "exchangeRate": 11.7,
    "fee": 10,
    "feePercent": 0.5,
    "total": 2010,
    "recipientName": "...",
    "recipientCountry": "Serbia",
    "fromAccount": "DNB",
    "eta": "1-2 business days",
    "createdAt": "..."
  }
}

Error Responses:

Status Code Condition
400 bad_request Missing/invalid fields
400 no_bank_account No linked bank account
402 insufficient_balance Bank account 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 a QR payment to a merchant.

Field Source
File app/api/transactions/qr-payment/route.ts
Auth Required
Rate Limit 10 req/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:

  1. Verify merchant exists
  2. Get user's primary bank account
  3. Fee: 1% of amount
  4. Debit bank account (atomic transaction)
  5. Create transaction with status completed (instant)

Success Response (201):

{
  "data": {
    "id": "tx_qr_...",
    "type": "qr_payment",
    "status": "completed",
    "amount": 129,
    "currency": "NOK",
    "fee": 1.29,
    "feePercent": 1,
    "merchantName": "Ahmetov Kebab",
    "merchantId": "mer_1",
    "fromAccount": "DNB",
    "createdAt": "..."
  }
}

Recipients

GET /api/recipients

List user's recipients with pagination.

Field Source
File app/api/recipients/route.ts
Auth Required

Query Parameters: page (default 1), limit (default 20, max 50)

Bank account numbers are masked in response (e.g., *****5678).

Supported Countries: RS (Serbia), BA (Bosnia), PL (Poland), PK (Pakistan), TR (Turkey)


POST /api/recipients

Add a new recipient.

Field Source
File app/api/recipients/route.ts
Auth Required

Request Body:

Field Type Required Validation
name string Yes validateName()
country string Yes Must be in supported list
currency string Yes -
bankAccount string Yes -
bankName string No Sanitized to 200 chars

DELETE /api/recipients/[id]

Delete a recipient.

Field Source
File app/api/recipients/[id]/route.ts
Auth Required

Returns 204 No Content on success. Returns 404 if recipient not found or not owned by user.


Cards (FUTURE — feature-flagged, all flags default to false)

Note: The entire Cards section is a FUTURE feature, gated behind feature flags. All card-related feature flags default to false. These endpoints exist in code but return 404 when flags are disabled. Cards require a card issuing partner (e.g., Stripe Issuing) before activation.

GET /api/cards

List user's cards (excludes cancelled).

Field Source
File app/api/cards/route.ts
Auth Required

POST /api/cards

Create a new card (virtual or physical).

Field Source
File app/api/cards/route.ts
Auth Required

Request Body:

Field Type Required Notes
type string No virtual (default) or physical

GET /api/cards/[id]

Get card details. Card number is masked (---- ---- ---- XXXX), CVV is hidden (---).

Field Source
File app/api/cards/[id]/route.ts
Auth Required

PCI-DSS compliant: never exposes full card number or CVV.


PATCH /api/cards/[id]

Freeze or unfreeze a card.

Field Source
File app/api/cards/[id]/route.ts
Auth Required

Request Body: { "status": "active" | "frozen" }


DELETE /api/cards/[id]

Cancel a card (soft delete — sets status to cancelled).

Field Source
File app/api/cards/[id]/route.ts
Auth Required

POST /api/cards/[id]/physical

Order physical version of a virtual card.

Field Source
File app/api/cards/[id]/physical/route.ts
Auth Required
Feature Flag physicalCards (returns 404 if disabled)

Request Body: { "address": "..." } (min 10 chars)


POST /api/cards/[id]/pin

Set PIN for a card.

Field Source
File app/api/cards/[id]/pin/route.ts
Auth Required
Feature Flag cardPin (returns 404 if disabled)

Request Body: { "pin": "1234" } (exactly 4 digits)

PIN is hashed with bcrypt before storage.


GET /api/cards/[id]/limits

Get spending limits for a card.

Field Source
File app/api/cards/[id]/limits/route.ts
Auth Required
Feature Flag spendingLimits (returns 404 if disabled)

PUT /api/cards/[id]/limits

Set a spending limit for a card.

Field Source
File app/api/cards/[id]/limits/route.ts
Auth Required
Feature Flag spendingLimits (returns 404 if disabled)

Request Body:

Field Type Required Validation
limitType string Yes daily, weekly, monthly, or transaction
amount number Yes Must be positive

Replaces any existing limit of the same type.


Exchange Rates

GET /api/rates

Get all exchange rates from NOK.

Field Source
File app/api/rates/route.ts
Auth None
Rate Limit 120 req/min per IP

Success 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 a specific currency pair.

Field Source
File app/api/rates/[currency]/route.ts
Auth None
Rate Limit 120 req/min per IP

Response includes fee: 0.005 (0.5% remittance fee).


Notifications

GET /api/notifications

List all notifications for user.

Field Source
File app/api/notifications/route.ts
Auth Required
Feature Flag notifications (default: enabled)

PATCH /api/notifications

Mark notifications as read.

Field Source
File app/api/notifications/route.ts
Auth Required
Feature Flag notifications

Request Body: { "notificationIds": ["noti_..."] }

  • Max 100 IDs per request
  • IDs validated against format ^[a-z]+_[a-f0-9]{16}$

Settings

GET /api/settings

Get user settings (creates defaults if none exist).

Field Source
File app/api/settings/route.ts
Auth Required

Defaults: currency=NOK, language=nb, pushEnabled=true, emailEnabled=true


PATCH /api/settings

Update user settings.

Field Source
File app/api/settings/route.ts
Auth Required

Request Body (all optional):

Field Type Validation
currency string Whitelist: EUR, USD, GBP, BAM, CHF, PLN, NOK, RSD, TRY, PKR
language string Whitelist: nb, en, bs, sq
pushEnabled boolean -
emailEnabled boolean -

Merchants

POST /api/merchants/register

Register as a merchant.

Field Source
File app/api/merchants/register/route.ts
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

Upgrades user role to merchant. Returns a QR code URI (drop://pay/{merchantId}).


GET /api/merchants/dashboard

Get merchant dashboard stats.

Field Source
File app/api/merchants/dashboard/route.ts
Auth Required (merchant role)

Query Parameters: periodtoday (default), week, month

Returns: revenue, transactionCount, fees, netRevenue, nextPayout, payoutTime.


GET /api/merchants/qr

Get merchant QR code data.

Field Source
File app/api/merchants/qr/route.ts
Auth Required (merchant role)

Returns: merchantId, businessName, qrValue (drop://pay/{id}), address.


GET /api/merchants/transactions

List merchant's QR payment transactions with pagination.

Field Source
File app/api/merchants/transactions/route.ts
Auth Required (merchant role)

Query Parameters: page, limit

Customer names are partially anonymized (first name + last initial).


GDPR & Compliance

GET /api/user/data-export

Export all user data (GDPR right to data portability).

Field Source
File app/api/user/data-export/route.ts
Auth Required

Creates a data_access_request record with type export and status completed.

Success Response (200):

{
  "data": {
    "user": {
      "id": "usr_...",
      "email": "...",
      "first_name": "...",
      "last_name": "...",
      "phone": "+47...",
      "date_of_birth": "1995-03-15",
      "kyc_status": "approved",
      "role": "user",
      "created_at": "..."
    },
    "transactions": [ {...}, {...} ],
    "recipients": [ {...}, {...} ],
    "bankAccounts": [ {...} ],
    "settings": { "currency": "NOK", "language": "nb", ... },
    "consents": [ {...}, {...} ]
  },
  "exportedAt": "2026-02-17T..."
}

DELETE /api/user/account

Request account deletion (GDPR right to erasure).

Field Source
File app/api/user/account/route.ts
Auth Required

Behavior:

  • Soft-deletes user (sets deleted_at timestamp)
  • Revokes all active sessions
  • Creates data_access_request with type erasure and status completed
  • Important: Data retained for 5 years per AML/KYC legal requirements (hvitvaskingsloven)

Success Response (200):

{
  "message": "Account scheduled for deletion",
  "retentionNote": "Data retained for 5 years per AML requirements"
}

GET /api/consents

List user's GDPR consents.

Field Source
File app/api/consents/route.ts
Auth Required

Success Response (200):

{
  "data": [
    {
      "id": "con_...",
      "user_id": "usr_...",
      "consent_type": "terms",
      "granted": 1,
      "granted_at": "2026-02-17T...",
      "withdrawn_at": null,
      "ip_address": "192.0.2.1"
    }
  ]
}

POST /api/consents

Grant or withdraw a consent.

Field Source
File app/api/consents/route.ts
Auth Required

Request Body:

Field Type Required Validation
consentType string Yes Must be one of: terms, privacy, marketing, cookies_analytics, cookies_marketing
granted boolean Yes true to grant, false to withdraw

Behavior:

Success Response (200 for update, 201 for new):

{
  "data": {
    "id": "con_...",
    "consent_type": "marketing",
    "granted": 1,
    "granted_at": "2026-02-17T...",
    "withdrawn_at": null,
    "ip_address": "192.0.2.1"
  }
}

Error Responses:

Status Code Condition
400 bad_request Invalid consent type or missing fields

GET /api/complaints

List user's complaints.

Field Source
File app/api/complaints/route.ts
Auth Required

Query Parameters:

Param Type Default Notes
page int 1 Pagination page number
limit int 10 Items per page, max 100

Success Response (200):

{
  "data": [
    {
      "id": "cmp_...",
      "category": "transaction",
      "subject": "Transaction delayed",
      "description": "My remittance to Serbia is delayed...",
      "status": "received",
      "resolution": null,
      "created_at": "2026-02-17T...",
      "resolved_at": null
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 3,
    "totalPages": 1
  }
}

POST /api/complaints

Submit a complaint (Finansavtaleloven §3-53 compliance).

Field Source
File app/api/complaints/route.ts
Auth Required

Request Body:

Field Type Required Validation
category string Yes Must be one of: transaction, service, fees, privacy, technical, other
subject string Yes Max 200 chars, sanitized
description string Yes Max 2000 chars, sanitized

Success Response (201):

{
  "data": {
    "id": "cmp_...",
    "category": "fees",
    "subject": "High transfer fee",
    "description": "...",
    "status": "received",
    "created_at": "2026-02-17T..."
  },
  "commitmentNote": "We will review and respond to your complaint within 15 business days per Finansavtaleloven §3-53"
}

Error Responses:

Status Code Condition
400 bad_request Invalid category or empty fields

POST /api/transactions/disclosure

Get full transaction fee and exchange rate disclosure before initiating payment.

Field Source
File app/api/transactions/disclosure/route.ts
Auth Required

Request Body:

Field Type Required Notes
type string Yes remittance or qr_payment
amount number Yes Must be positive
currency string No Defaults to NOK
recipientId string Conditional Required for remittance

Success Response (200):

{
  "amount": 2000,
  "fee": 10,
  "feePercentage": 0.5,
  "exchangeRate": 10.17,
  "receiveAmount": 20340,
  "receiveCurrency": "RSD",
  "estimatedDelivery": "1-2 business days",
  "totalCost": 2010
}

Fee Calculation:

  • Remittance: 0.5% of amount
  • QR payment: 1.0% of amount

Delivery Time:

  • QR payment: "Instant"
  • Remittance (EEA): "1-2 business days"
  • Remittance (non-EEA): "2-4 business days"

GET /api/transactions/[id]/receipt

Get transaction receipt with full details.

Field Source
File app/api/transactions/[id]/receipt/route.ts
Auth Required

Success Response (200):

{
  "data": {
    "transactionId": "tx_rem_1",
    "date": "2026-02-17T...",
    "type": "remittance",
    "amount": 2000,
    "currency": "NOK",
    "fee": 10,
    "exchangeRate": 10.17,
    "receiveAmount": 20340,
    "receiveCurrency": "RSD",
    "recipient": {
      "name": "Mama Jasmina",
      "country": "RS"
    },
    "reference": "tx_rem_1",
    "status": "completed",
    "estimatedCompletion": null,
    "completedAt": "2026-02-17T..."
  }
}

Error Responses:

Status Code Condition
404 not_found Transaction not found or not owned by user

Health Check

GET /api/health

System health check (no auth required).

Field Source
File app/api/health/route.ts
Auth None

Success Response (200):

{
  "status": "ok",
  "version": "0.1.0",
  "uptime": 3600,
  "db": "connected",
  "dbLatencyMs": 1,
  "timestamp": "..."
}

Returns 503 with status: "error" if database is unreachable.