Skip to main content

API Reference

Drop Backend API Reference

Project:Auto-generated {{PROJECT_NAME}}from Version:source {{VERSION}}code Date:analysis. {{DATE}}All Author:file {{AUTHOR}}references Status:are Draftrelative |to In Review | Approved Reviewers: {{REVIEWERS}}src/drop-app/src/.

DocumentOverview

History

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.

VersionField DateAuthorChangesSource
0.1File {{DATE}}app/api/auth/register/route.ts
Auth {{AUTHOR}}None
Rate Limit Initial10 draftreq/min per IP

1. API Overview & Conventions

APIRequest style: {{RESTful HTTP/JSON | GraphQL | gRPC}} API version strategy: {{URL versioning: /v1/ | Header versioning}} OpenAPI spec: {{https://api.domain.com/docs/openapi.json}} Interactive docs: {{https://api.domain.com/docs}}

Design conventions:Body:

  • Resources named as plural nouns: /users, /orders, /products
  • HTTP methods map to CRUD: GET (read), POST (create), PUT (replace), 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
EnvironmentField BaseType RequiredValidation
Developmentemail http://localhost:4000/v1stringYesRFC-like regex, unique
Stagingpassword https://api-staging.{{domain.com}}/v1stringYesMin 8 chars, must contain letters + digits
ProductionfirstNamestringYes https://api.{{domain.com}}/v1validateName() — 1-100 chars, at least one letter, no HTML/script
lastNamestringYesSame as firstName
phonestringNoInternational format +XXXXXXXXXXXX (8-15 digits)
dateOfBirthstringYesISO date string, must be >= 18 years old

3. Authentication

Method: Bearer Token (JWT)

Obtain tokens: POST /auth/login (see Auth section below)

Include in requests:

Authorization: Bearer <access_token>

Token lifetimes:

  • Access token: 15 minutes
  • Refresh token: 30 days (rotate on use)

Refresh tokens: POST /auth/refresh with { "refreshToken": "..." } 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. Common Request/Response Headers

4.1 Request Headers

HeaderRequiredDescription
AuthorizationYes (auth routes)Bearer <token>
Content-TypeYes (POST/PUT/PATCH)application/json
AcceptNoapplication/json (default)
X-Request-IDNoClient-provided idempotency ID
Accept-LanguageNoen, nb, etc. — affects response locale

4.2Success Response Headers

HeaderDescription
Content-Typeapplication/json; charset=utf-8
X-Request-IDEcho of client request ID (or server-generated)
X-RateLimit-LimitTotal requests allowed in window
X-RateLimit-RemainingRemaining requests in current window
X-RateLimit-ResetUnix timestamp when window resets
Retry-AfterSeconds to wait (set when 429 returned)

5. Error Response Format

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format",
        "code": "INVALID_FORMAT"
      }
    ],
    "requestId": "req_7f3a2b1c",
    "timestamp": "2024-01-15T10:30:00Z"
  }
}

Standard error codes:

HTTP StatusError CodeMeaning
400VALIDATION_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 downtime

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 OK201):

{
  "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
  "refreshToken": "dGhpcyBpcyBhIHJlZnJlc2g...",
  "expiresIn": 900,
  "user"data": {
    "id": "usr_01HX7.usr_...",
    "email": "[email protected]",
    "name"firstName": "John Doe"...",
    "role"lastName": "user"...",
    "dateOfBirth": "...",
    "kycStatus": "pending",
    "createdAt": "2026-..."
  }
}

Error responses:Responses:

Status Code Condition
401400bad_requestInvalid JSON body
409conflictEmail already registered
422validation_errorField validation failures (returned in details array)
429rate_limitedToo many requests

POST /api/auth/login

Authenticate with email and password.

FieldSource
File INVALID_CREDENTIALSapp/api/auth/login/route.ts
WrongAuthNone
Rate Limit10 req/min per IP

Request Body:

FieldTypeRequired
emailstringYes
passwordstringYes

Success Response (200):

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

Error Responses:

StatusCodeCondition
400bad_requestMissing email or password
401unauthorizedInvalid credentials
429 RATE_LIMITEDrate_limited >Too 5many failed attempts in 15 minrequests

POST

GET /api/auth/refresh

Rotate access and refresh tokens.

Auth required: No

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 Usersme

Endpoints:Get current authenticated user with bank accounts.

MethodField PathDescriptionAuthSource
GETFile /usersapp/api/auth/me/route.tsList users (paginated)Admin
GETAuth /users/:idGet user by IDSelf or Admin
POST/usersCreate userAdmin
PATCH/users/:idUpdate user fieldsSelf or Admin
DELETE/users/:idDelete userRequired (soft)Admin
GET/users/meGet current userAuthenticated
PATCH/users/meUpdate current userAuthenticatedcookie)

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.

FieldSource
Fileapp/api/auth/logout/route.ts
AuthRequired (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).

FieldSource
Fileapp/api/auth/refresh/route.ts
AuthRequired (cookie)

Success Response (200):

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

Transactions

GET /users

api/transactions

List usersuser's transactions with pagination and filtering.

required:Admin

FieldSource
Fileapp/api/transactions/route.ts
Auth Required

Query parameters:Parameters:

ParameterParam Type Default DescriptionNotes
page integerint 1 PageMin number1
pageSizelimit integerint 2520 ItemsMin per1, pageMax (max: 100)50
searchtype string - Search nameremittance or email (min 2 chars)
rolestringFilter by role: admin, user, viewerqr_payment
status string active- Filter by status: activeprocessing, inactivecompleted, all
sortstringcreatedAtSort field
dirstringdescSort direction:or asc, descfailed

Success Response 200 OK(200):

{
  "data": [
    {
      "id": "usr_01HX7..."tx_rem_1",
      "email"type": "[email protected]",
      "name": "Jane Doe",
      "role": "user"remittance",
      "status": "active"completed",
      "amount": -2000,
      "currency": "NOK",
      "recipientName": "Mama Jasmina",
      "createdAt": "2024-01-15T10:30:00Z",
      ..."updatedAt": "2024-01-15T10:30:00Z"
    }
  ],
  "pagination": { "page": 1, "pageSize"limit": 25,20, "total": 142,
    "totalPages": 63 }
}

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


GET /users/:id

api/transactions/[id]

AuthGet required:single Selftransaction ordetails Admin

with

Pathexchange parameters:rate info.

ParameterField TypeDescriptionSource
Fileidapp/api/transactions/[id]/route.ts
UUIDAuth User IDRequired

Success Response 200 OK(200):

{
  "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": "...",
    "completedAt": "..."
  }
}

GET /api/transactions/summary

Get transaction summary statistics (all-time and this month).

FieldSource
Fileapp/api/transactions/summary/route.ts
AuthRequired

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

FieldSource
Fileapp/api/transactions/remittance/route.ts
AuthRequired
Rate Limit10 req/min per IP
KYCMust be approved

Request Body:

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

StatusCodeCondition
400bad_requestMissing/invalid fields
400no_bank_accountNo linked bank account
402insufficient_balanceBank account balance too low
403kyc_requiredKYC not approved
404not_foundRecipient not found
422validation_errorUnsupported currency corridor

POST /api/transactions/qr-payment

Create a QR payment to a merchant.

FieldSource
Fileapp/api/transactions/qr-payment/route.ts
AuthRequired
Rate Limit10 req/min per IP

Request Body:

FieldTypeRequiredValidation
merchantIdstringYesMust exist
amountnumberYes1-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.

FieldSource
Fileapp/api/recipients/route.ts
AuthRequired

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.

FieldSource
Fileapp/api/recipients/route.ts
AuthRequired

Request Body:

FieldTypeRequiredValidation
namestringYesvalidateName()
countrystringYesMust be in supported list
currencystringYes-
bankAccountstringYes-
bankNamestringNoSanitized to 200 chars

DELETE /api/recipients/[id]

Delete a recipient.

FieldSource
Fileapp/api/recipients/[id]/route.ts
AuthRequired

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

FieldSource
Fileapp/api/cards/route.ts
AuthRequired

POST /api/cards

Create a new card (virtual or physical).

FieldSource
Fileapp/api/cards/route.ts
AuthRequired

Request Body:

FieldTypeRequiredNotes
typestringNovirtual (default) or physical

GET /api/cards/[id]

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

FieldSource
Fileapp/api/cards/[id]/route.ts
AuthRequired

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


PATCH /api/cards/[id]

Freeze or unfreeze a card.

FieldSource
Fileapp/api/cards/[id]/route.ts
AuthRequired

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


DELETE /api/cards/[id]

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

FieldSource
Fileapp/api/cards/[id]/route.ts
AuthRequired

POST /api/cards/[id]/physical

Order physical version of a virtual card.

FieldSource
Fileapp/api/cards/[id]/physical/route.ts
AuthRequired
Feature FlagphysicalCards (returns 404 if disabled)

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


POST /api/cards/[id]/pin

Set PIN for a card.

FieldSource
Fileapp/api/cards/[id]/pin/route.ts
AuthRequired
Feature FlagcardPin (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.

FieldSource
Fileapp/api/cards/[id]/limits/route.ts
AuthRequired
Feature FlagspendingLimits (returns 404 if disabled)

PUT /api/cards/[id]/limits

Set a spending limit for a card.

FieldSource
Fileapp/api/cards/[id]/limits/route.ts
AuthRequired
Feature FlagspendingLimits (returns 404 if disabled)

Request Body:

FieldTypeRequiredValidation
limitTypestringYesdaily, weekly, monthly, or transaction
amountnumberYesMust be positive

Replaces any existing limit of the same type.


Exchange Rates

GET /api/rates

Get all exchange rates from NOK.

FieldSource
Fileapp/api/rates/route.ts
AuthNone
Rate Limit120 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.

FieldSource
Fileapp/api/rates/[currency]/route.ts
AuthNone
Rate Limit120 req/min per IP

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


Notifications

GET /api/notifications

List all notifications for user.

FieldSource
Fileapp/api/notifications/route.ts
AuthRequired
Feature Flagnotifications (default: enabled)

PATCH /api/notifications

Mark notifications as read.

FieldSource
Fileapp/api/notifications/route.ts
AuthRequired
Feature Flagnotifications

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

FieldSource
Fileapp/api/settings/route.ts
AuthRequired

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


PATCH /api/settings

Update user settings.

FieldSource
Fileapp/api/settings/route.ts
AuthRequired

Request Body (all optional):

FieldTypeValidation
currencystringWhitelist: EUR, USD, GBP, BAM, CHF, PLN, NOK, RSD, TRY, PKR
languagestringWhitelist: nb, en, bs, sq
pushEnabledboolean-
emailEnabledboolean-

Merchants

POST /api/merchants/register

Register as a merchant.

FieldSource
Fileapp/api/merchants/register/route.ts
AuthRequired

Request Body:

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

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


GET /api/merchants/dashboard

Get merchant dashboard stats.

FieldSource
Fileapp/api/merchants/dashboard/route.ts
AuthRequired (merchant role)

Query Parameters: period — today (default), week, month

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


GET /api/merchants/qr

Get merchant QR code data.

FieldSource
Fileapp/api/merchants/qr/route.ts
AuthRequired (merchant role)

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


GET /api/merchants/transactions

List merchant's QR payment transactions with pagination.

FieldSource
Fileapp/api/merchants/transactions/route.ts
AuthRequired (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).

FieldSource
Fileapp/api/user/data-export/route.ts
AuthRequired

Creates a data_access_request record with type export and status completed.

Success Response (200):

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

DELETE /api/user/account

Request account deletion (GDPR right to erasure).

FieldSource
Fileapp/api/user/account/route.ts
AuthRequired

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 /cdn.domain.com/avatars/.api/consents

List user's GDPR consents.

FieldSource
Fileapp/api/consents/route.ts
AuthRequired

Success Response (200):

{
  "data": [
    {
      "id": "con_...",
      "bio"user_id": "Software developer"
  }usr_...",
      "createdAt"consent_type": "2024-01-15T10:30:00Z"terms",
      "updatedAt"granted": 1,
      "granted_at": "2024-01-15T10:30:00Z"2026-02-17T...",
      "withdrawn_at": null,
      "ip_address": "192.0.2.1"
    }
  ]
}

POST /api/consents

Grant or withdraw a consent.

FieldSource
Fileapp/api/consents/route.ts
AuthRequired

Request Body:

FieldTypeRequiredValidation
consentTypestringYesMust be one of: terms, privacy, marketing, cookies_analytics, cookies_marketing
grantedbooleanYestrue 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:Responses:

StatusCodeCondition
400bad_requestInvalid consent type or missing fields

GET /api/complaints

List user's complaints.

FieldSource
Fileapp/api/complaints/route.ts
AuthRequired

Query Parameters:

ParamTypeDefaultNotes
pageint1Pagination page number
limitint10Items 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).

FieldSource
Fileapp/api/complaints/route.ts
AuthRequired

Request Body:

FieldTypeRequiredValidation
categorystringYesMust be one of: transaction, service, fees, privacy, technical, other
subjectstringYesMax 200 chars, sanitized
descriptionstringYesMax 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:

StatusCodeCondition
400bad_requestInvalid category or empty fields

POST /api/transactions/disclosure

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

FieldSource
Fileapp/api/transactions/disclosure/route.ts
AuthRequired

Request Body:

FieldTypeRequiredNotes
typestringYesremittance or qr_payment
amountnumberYesMust be positive
currencystringNoDefaults to NOK
recipientIdstringConditionalRequired 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.

FieldSource
Fileapp/api/transactions/[id]/receipt/route.ts
AuthRequired

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:

foundornotowned
Status Code Condition
404 NOT_FOUNDnot_found User doesTransaction not exist
403 FORBIDDENNon-admin accessing anotherby user

POST

Health /users

Check

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

api/health

AuthSystem required:health Self or Admin

Request bodycheck (allno fieldsauth optional):

{
  "name": "Updated Name",
  "profile": {
    "bio": "Updated bio"
  }
}

Response 200 OK: Updated user object.


DELETE /users/:id

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

Auth required: Admin

Response 204 No Content


6.3 {{RESOURCE_NAME}}

Endpoints:

MethodField PathDescriptionAuthSource
GETFile /{{resource}}List {{resource}}{{Auth level}}app/api/health/route.ts
GETAuth /{{resource}}/:idGet by ID{{Auth level}}
POST/{{resource}}Create{{Auth level}}
PATCH/{{resource}}/:idUpdate{{Auth level}}
DELETE/{{resource}}/:idDelete{{Auth level}}None

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


7. Pagination Format

All list endpoints return the same pagination envelope:

{
  "data": [...],
  "pagination": {
    "page": 1,
    "pageSize": 25,
    "total": 142,
    "totalPages": 6,
    "hasNextPage": true,
    "hasPreviousPage": false
  }
}

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

GET /feed?cursor=eyJpZCI6MTIzfQ&pageSize=20

Response includes nextCursor — pass as cursor in next request.


8. Filtering & Sorting Conventions

Filter parameters:

GET /orders?status=pending&createdAt[gte]=2024-01-01&total[lte]=1000
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)200):

{
  "error": {
    "code"status": "RATE_LIMITED"ok",
  "message"version": "Too many requests. Please retry after 60 seconds."0.1.0",
  "retryAfter"uptime": 603600,
  }"db": "connected",
  "dbLatencyMs": 1,
  "timestamp": "..."
}

Returns

11.503 Codewith Examples

cURL

# Login
curl -X POST https://api.domain.com/v1/auth/login \
  -Hstatus: "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>"
error"

JavaScript (fetch)

const response = await fetch('https://api.domain.com/v1/users', {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json',
  },
}); if (!response.ok)database {is constunreachable.

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

RoleNameDateSignature
Author
Backend Lead
Tech Lead
Product Owner