Skip to main content

API Reference

API Reference

Project: {{PROJECT_NAME}}Drop Version: {{VERSION}}0.1.0 Date: {{DATE}}2026-02-23 Author: {{AUTHOR}}Platform Architect (AI) Status: Draft | In Review | Approved Reviewers: {{REVIEWERS}}Alem Bašić (CEO)

Document History

Version Date Author Changes
0.1 {{DATE}}2026-02-23 {{AUTHOR}}Platform Architect (AI) InitialCompiled draftfrom source code analysis

1.Overview

Drop uses Next.js App Router API Overviewroutes &(app/api/). ConventionsThe application

uses a APIPSD2 style:pass-through model {{RESTful HTTP/JSONDrop |never GraphQLholds |customer gRPC}}money. APIAll versionfunds strategy:remain {{URLin versioning:users' /v1/bank |accounts; HeaderDrop versioning}}uses OpenAPI spec: {{https://api.domain.com/docs/openapi.json}} Interactive docs: {{https://api.domain.com/docs}}

Design conventions:

  • Resources named as plural nouns: /users, /orders, /products
  • HTTP methods mapAISP to CRUD:read GETbalances (read),and POSTPISP (create),to PUTinitiate (replace),payments.

    PATCH (update), DELETE (remove)
  • Response format: always JSON
  • Timestamps: ISO 8601 UTC (2024-01-15T10:30:00Z)
  • IDs: UUID v4 strings
  • Booleans: true / false (never 1 / 0)
  • Empty collections: [] (never null)
  • Missing optional fields: omitted (never null unless semantically null)

2.

Base URLs

URL
Environment Base URL
DevelopmentProduction http:https://localhost:4000/v19ef3szvvsb.eu-west-1.awsapprunner.com (future: https://getdrop.no)
Staging https://api-drop-staging.{{domain.com}}/v1fly.dev
ProductionLocal https:http://api.localhost:3000

Response Envelope

// Success
{ "data": {domain.com} ... } }

/v1/ 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 GroupLimit
BankID initiate/callback10/min per IP
Transaction creation (remittance, qr-payment)10/min per IP
Exchange rates120/min per IP
All other endpointsNo IP-level limit (auth required)

3.Authentication AuthenticationEndpoints

GET /api/auth/bankid

Initiate BankID OIDC login flow.

PropertyValue
AuthNone
Rate Limit10/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.

PropertyValue
AuthNone (validates state cookie)
Rate Limit10/min per IP

Method:Query params: Bearercode, Token (JWT)state

ObtainSuccess: tokens:Redirects to POST/dashboard, sets drop_token cookie. Creates user with kyc_status=approved on first login. Verifies age >= 18 from BankID pid.


GET /api/auth/loginme

Get current authenticated user with linked bank accounts.

Authsectionbelow)

PropertyValue
AuthRequired (seecookie)

IncludeResponse in requests:(200):

Authorization:{
  Bearer"data": <access_token>{
    "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"
  }
}

TokenNote: lifetimes:balance values are AISP-read from user's real bank — NOT Drop-held funds.


  • Access

    POST token:/api/auth/logout

    15

    Logout minutes

  • Refreshrevoke token:all 30sessions.

    days onuse)
    PropertyValue
    AuthRequired (rotatecookie)

    RefreshResponse tokens:(200): POST /auth/refresh with { "refreshToken"message": "Logged out" }


    POST /api/auth/refresh

    Refresh JWT token.

    PropertyValue
    AuthRequired (cookie)

    Response (200): { "data": { "userId": "usr_...", "email": "...", "role": "user" } } in body.

    API Key authentication (machine-to-machine):

    X-API-Key: <api_key>
    

    API keys are scoped and managed at {{https://dashboard.domain.com/api-keys}}.


    4.

    Deprecated CommonEndpoints Request/Response(410 Headers

    4.1 Request HeadersGone)

    viaBankID
    HeaderEndpoint RequiredDescriptionReplacement
    AuthorizationPOST /api/auth/login YesBankID (auth routes)Bearer <token>OIDC
    Content-TypePOST /api/auth/register YesAuto (POST/PUT/PATCH) application/jsonBankID
    AcceptPOST /api/auth/verify-otp No application/jsonreplaces (default)
    X-Request-IDNoClient-provided idempotency ID
    Accept-LanguageNoen, nb, etc. — affects response localeOTP

    Transaction Endpoints

    4.2GET Response/api/transactions

    Headers

    List user transactions with pagination and filtering.

    HeaderProperty DescriptionValue
    AuthRequired

    Query Parameters:

    charset=utf-8 ,
    ParamTypeDefaultNotes
    Content-Typepage int1Min 1
    application/json;limit int20Min 1, Max 50
    typestringremittance or qr_payment
    X-Request-IDstatus Echo of client request ID (or server-generated)string
    X-RateLimit-Limitprocessing Totalcompleted, requestsor allowed in window
    X-RateLimit-RemainingfailedRemaining requests in current window
    X-RateLimit-ResetUnix timestamp when window resets
    Retry-AfterSeconds to wait (set when 429 returned)

    5. Error

    Response Format

    (200):

    {
      "error": {
        "code": "VALIDATION_ERROR",
        "message": "Request validation failed",
        "details"data": [
        { "field"id": "email"tx_rem_1", "message"type": "Invalid email format"remittance", "code"status": "INVALID_FORMAT"completed",
          "amount": -2000, "currency": "NOK", "recipientName": "Mama Jasmina", "createdAt": "..." }
      ],
      "requestId"pagination": "req_7f3a2b1c",{ "timestamp"page": 1, "2024-01-15T10:30:00Z"limit": 20, "total": 3 }
    }
    

    GET /api/transactions/[id]

    StandardGet errorsingle codes:transaction details.

    HTTP StatusProperty Error CodeMeaningValue
    400Auth VALIDATION_ERRORRequest body / params failed validation
    400BAD_REQUESTMalformed request
    401UNAUTHORIZEDMissing or invalid authentication
    401TOKEN_EXPIREDJWT has expired — refresh required
    403FORBIDDENAuthenticated but lacks permission
    404NOT_FOUNDResource does not exist
    409CONFLICTResource already exists / version conflict
    422UNPROCESSABLEValid format but business rule violation
    429RATE_LIMITEDToo many requests
    500INTERNAL_ERRORUnexpected server error
    503SERVICE_UNAVAILABLETemporary downtimeRequired

    6. Resources


    6.1 Authentication

    POST /auth/login

    Authenticate user and receive token pair.

    Auth required: No

    Request body:

    {
      "email": "[email protected]",
      "password": "{{password}}"
    }
    

    Response 200 OK(200):

    {
      "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
      "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2g...",
      "expiresIn": 900,
      "user"data": {
        "id": "usr_01HX7.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": "...", "email"completedAt": "[email protected]",
        ..."name": "John Doe",
        "role": "user"
      }
    }
    

    GET /api/transactions/summary

    Get all-time + this-month statistics.

    PropertyValue
    AuthRequired

    ErrorResponse responses:(200): { "data": { "allTime": { totalCount, totalSent, totalPaid, remittanceCount, qrPaymentCount }, "thisMonth": { ... } } }


    POST /api/transactions/remittance

    Create international money transfer.

    PropertyValue
    AuthRequired
    Rate Limit10/min per IP
    KYCkyc_status must be approved

    Request Body:

    FieldTypeRequiredValidation
    recipientIdstringYesMust belong to current user
    amountnumberYes100–50,000 NOK, max 2 decimal places
    currencystringNoDefaults to NOK
    bankAccountIdstringNoDefaults 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:

    in15min
    Status Code Condition
    401400 INVALID_CREDENTIALSbad_request WrongMissing/invalid email or passwordfields
    429400 RATE_LIMITEDno_bank_account >No 5linked failedbank attemptsaccount
    402insufficient_balanceBalance too low
    403kyc_requiredKYC not approved
    404not_foundRecipient not found
    422validation_errorUnsupported currency corridor

    POST /auth/refresh

    api/transactions/qr-payment

    RotateCreate accessQR andpayment refreshto tokens.a merchant.

    required:No

    PropertyValue
    Auth Required
    Rate Limit10/min per IP

    Request body:

    { "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2g..." }
    

    Response 200 OK: Same as login response.


    POST /auth/logout

    Revoke refresh token.

    Auth required: Yes (Bearer)

    Request body:

    { "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2g..." }
    

    Response 204 No Content


    6.2 Users

    Endpoints:Body:

    MethodField PathType DescriptionRequired Validation
    merchantIdstringYesMust exist
    amountnumberYes1–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).

    PropertyValue
    AuthRequired

    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.

    PropertyValue
    AuthRequired

    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).

    PropertyValue
    AuthRequired

    Query Params: page, limit (max 50). Supported countries: RS, BA, PL, PK, TR.


    POST /api/recipients

    Add a recipient.

    PropertyValue
    AuthRequired

    Request Body: { name, country, currency, bankAccount, bankName? }


    DELETE /api/recipients/[id]

    Delete a recipient. Response (204). 404 if not found/owned.

    PropertyValue
    AuthRequired

    Exchange Rate Endpoints

    GET /api/rates

    Get all NOK exchange rates (public).

    PropertyValue
    AuthNone
    Rate Limit120/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).

    PropertyValue
    AuthNone
    Rate Limit120/min per IP

    Merchant Endpoints

    POST /api/merchants/register

    Register as merchant (upgrades role to merchant).

    PropertyValue
    AuthRequired

    Request Body:

    FieldTypeRequiredValidation
    businessNamestringYesvalidateName()
    orgNumberstringYesExactly 9 digits, unique
    addressstringNoSanitized to 300 chars
    bankAccountstringYesPayout account

    Returns QR code URI: drop://pay/{merchantId}


    GET /api/merchants/dashboard

    Merchant revenue stats.

    PropertyValue
    AuthRequired (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.

    PropertyValue
    AuthRequired (merchant role)

    Returns: { merchantId, businessName, qrValue: "drop://pay/{id}", address }


    GET /api/merchants/transactions

    List merchant's QR payment transactions. Customer names partially anonymized.

    PropertyValue
    AuthRequired (merchant role)

    Notification Endpoints

    GET /api/notifications

    PropertyValue
    AuthRequired
    Feature Flagnotifications (default: enabled)

    PATCH /api/notifications

    Mark notifications as read. Max 100 IDs per request.

    PropertyValue
    AuthRequired
    Feature Flagnotifications

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


    Settings Endpoints

    GET /api/settings

    Get user settings (auto-creates defaults: currency=NOK, language=nb).

    PropertyValue
    AuthRequired

    PATCH /api/settings

    Update user settings.

    PropertyValue
    AuthRequired

    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).

    PropertyValue
    AuthRequired

    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.

    PropertyValue
    AuthRequired

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


    GET /api/consents

    List GDPR consents.

    PropertyValue
    AuthRequired

    POST /api/consents

    Grant or withdraw consent.

    PropertyValue
    AuthRequired

    Request Body:

    FieldTypeValidation
    consentTypestringterms, privacy, marketing, cookies_analytics, cookies_marketing
    grantedbooleantrue to grant, false to withdraw

    Records IP address with consent action.


    GET /api/complaints

    List user's complaints.

    PropertyValue
    AuthRequired

    Query Params: page, limit (max 100)


    POST /api/complaints

    Submit a complaint (Finansavtaleloven §3-53 — 15 business day response SLA).

    PropertyValue
    AuthRequired

    Request Body:

    FieldTypeValidation
    categorystringtransaction, service, fees, privacy, technical, other
    subjectstringMax 200 chars
    descriptionstringMax 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.

    userbyID
    EndpointFeature Flag
    GET /api/cards virtualCards
    POST /usersapi/cards List users (paginated)AdminvirtualCards
    GET /api/cards/[id] cardDetails
    PATCH /users/:idapi/cards/[id] GetcardFreeze
    DELETE /api/cards/[id] Self or AdminvirtualCards
    POST /api/cards/[id]/physical /usersCreate userAdmin
    PATCH/users/:idUpdate user fieldsSelf or Admin
    DELETE/users/:idDelete user (soft)Admin
    GET/users/meGet current userAuthenticated
    PATCH/users/meUpdate current userAuthenticated

    GET /users

    List users with pagination and filtering.

    Auth required: Admin

    Query parameters:

    ParameterTypeDefaultDescription
    pageinteger1Page number
    pageSizeinteger25Items per page (max: 100)
    searchstringSearch name or email (min 2 chars)
    rolestringFilter by role: admin, user, viewerphysicalCards
    statusPOST /api/cards/[id]/pin stringactiveFilter by status: active, inactive, allcardPin
    sortGET/PUT /api/cards/[id]/limits stringcreatedAtSort field
    dirstringdescSort direction: asc, descspendingLimits

    Card numbers always masked (---- ---- ---- XXXX). CVV never exposed. PCI-DSS compliant masking.


    Health Check

    GET /api/health

    System health — no auth required.

    Response 200 OK(200):

    {
      "data": [
        {
          "id": "usr_01HX7...",
          "email": "[email protected]",
          "name": "Jane Doe",
          "role": "user",
        "status": "active"ok",
        "createdAt"version": "2024-01-15T10:30:00Z"0.1.0",
        "updatedAt"uptime": "2024-01-15T10:30:00Z"
        }
      ],3600,
        "pagination"checks": {
          "page"db": 1,
        "pageSize": 25,
        "total": 142,
        "totalPages": 6
      }
    }
    

    GET /users/:id

    Auth required: Self or Admin

    Path parameters:

    ParameterTypeDescription
    idUUIDUser ID

    Response 200 OK:

    {
      "id": "usr_01HX7...",
      "email": "[email protected]",
      "name": "Jane Doe",
      "role": "user", "status": "active"pass", "profile"latencyMs": 2, "driver": "pg" },
          "services": { "avatarUrl"mode": "https://cdn.domain.com/avatars/...",production" "bio": "Software developer"}
        },
        "createdAt"timestamp": "2024-01-15T10:30:00Z",
      "updatedAt": "2024-01-15T10:30:00Z"
    }
    

    Error responses:

    StatusCodeCondition
    404NOT_FOUNDUser does not exist
    403FORBIDDENNon-admin accessing another user

    POST /users

    Auth required: Admin

    Request body:

    {
      "email": "[email protected]",
      "name": "New User",
      "role": "user",
      "password": "{{password}}"
    }
    

    Response 201 Created: Full user object (same as GET /users/:id)


    PATCH /users/:id

    Auth required: Self or Admin

    Request body (all fields optional):

    {
      "name": "Updated Name",
      "profile": {
        "bio": "Updated bio"2026-02-23T12:00:00.000Z"
      }
    }
    

    Response 200(503 OK— DB unreachable): Updated user object.


    DELETE /users/:id

    Soft-deletes user (sets deletedAt, anonymizes PII).

    Auth required: Admin

    Response 204 No Content


    6.3 {{RESOURCE_NAME}}

    Endpoints:

    MethodPathDescriptionAuth
    GET/{{resource}}List {{resource}}{{Auth level}}
    GET/{{resource}}/:idGet by ID{{Auth level}}
    POST/{{resource}}Create{{Auth level}}
    PATCH/{{resource}}/:idUpdate{{Auth level}}
    DELETE/{{resource}}/:idDelete{{Auth level}}

    TODO: Document all endpoints for this resource following the pattern above.


    7. Pagination Format

    All list endpoints return the same pagination envelope:

    { "data": [...]{ "status": "down", "pagination"checks": { "page"db": 1,{ "pageSize"status": 25,
        "total": 142,
        "totalPages": 6,
        "hasNextPage": true,
        "hasPreviousPage": falsefail" } } } }

    Cursor pagination (high-performance, for infinite scroll):

    GET /feed?cursor=eyJpZCI6MTIzfQ&pageSize=20
    

    Response includes nextCursor — pass as cursor in next request.


    Filter

      parameters:

    • Backend
      GETArchitecture
    • /orders?status=pending&createdAt[gte]=2024-01-01&total[lte]=1000
    • Middleware Design
    • External
    • Services Integration
    • Source
    • API Reference
      OperatorSuffixExample
      Equals(none)?status=active
      Greater than[gt]?price[gt]=100
      Greater than or equal[gte]?price[gte]=100
      Less than[lt]?price[lt]=500
      Less than or equal[lte]?price[lte]=500
      In list[in]?status[in]=active,pending
      Not in list[nin]?status[nin]=deleted

      Sort: ?sort=createdAt&dir=desc (default: createdAt desc)


      9. Webhooks Documentation

      Webhook endpoint: Configured per-account at {{https://dashboard.domain.com/webhooks}}

      Delivery: HTTP POST with JSON body, signed with HMAC-SHA256.

      Signature verification:

      const signature = req.headers['x-webhook-signature'];
      const computed = crypto
        .createHmac('sha256', webhookSecret)
        .update(rawBody)
        .digest('hex');
      const valid = crypto.timingSafeEqual(
        Buffer.from(signature), Buffer.from(computed)
      );
      

      Event envelope:

      {
        "id": "evt_01HX7...",
        "type": "user.created",
        "data": { /* resource object */ },
        "timestamp": "2024-01-15T10:30:00Z",
        "version": "1"
      }
      

      Available events:

      EventTrigger
      user.createdNew user registered
      user.updatedUser profile changed
      user.deletedUser account deleted
      {{resource}}.{{action}}{{Description}}

      Retry policy: 5 retries with exponential backoff. Undeliverable after 24 hours → marked as failed.


      10. Rate Limiting

      Endpoint GroupLimitWindow
      Public endpoints100 req1 minute
      Authenticated endpoints1000 req1 minute
      Admin endpoints5000 req1 minute
      Auth endpoints (login)5 req15 minutes
      Webhook deliveryN/A

      Response when rate limited (429 Too Many Requests):

      {
        "error": {
          "code": "RATE_LIMITED",
          "message": "Too many requests. Please retry after 60 seconds.",
          "retryAfter": 60
        }
      }
      

      11. Code Examples

      cURL

      # Login
      curl -X POST https://api.domain.com/v1/auth/login \
        -H "Content-Type: application/json" \
        -d '{"email": "[email protected]", "password": "secret"}'
      
      # Get users (with token)
      curl -X GET "https://api.domain.com/v1/users?page=1&pageSize=10" \
        -H "Authorization: Bearer <access_token>"
      

      JavaScript (fetch)

      const response = await fetch('https://api.domain.com/v1/users', {
        headers: {
          'Authorization': `Bearer ${accessToken}`,
          'Content-Type': 'application/json',
        },
      });
      
      if (!response.ok) {
        const error = await response.json();
        throw new Error(error.error.message);
      }
      
      const { data, pagination } = await response.json();
      

      Python

      import httpx
      
      client = httpx.Client(
          base_url="https://api.domain.com/v1",
          headers={"Authorization": f"Bearer {access_token}"}
      )
      
      response = client.get("/users", params={"page": 1, "pageSize": 10})
      response.raise_for_status()
      result = response.json()
      

      12. SDK Availability

      LanguagePackageRepositoryStatus
      TypeScript / JavaScript@{{company}}/api-client{{URL}}{{Available/Planned}}
      Python{{company}}-python{{URL}}{{Available/Planned}}
      Go{{company}}-go{{URL}}{{Planned}}

    Approval

    Alem
    Role Name Date Signature
    Author Platform Architect (AI) 2026-02-23
    Backend LeadReviewer
    Tech LeadApprover
    Product OwnerBašić