Skip to main content

API Reference

Drop Backend API Reference

Auto-generatedProject: from{{PROJECT_NAME}} sourceVersion: code{{VERSION}} analysis.Date: All{{DATE}} fileAuthor: references{{AUTHOR}} areStatus: relativeDraft to| src/drop-app/src/.In Review | Approved Reviewers: {{REVIEWERS}}

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

FieldVersion SourceDateAuthorChanges
File0.1 app/api/auth/register/route.ts
Auth{{DATE}} None
Rate Limit{{AUTHOR}} 10Initial req/min per IPdraft

1. API Overview & Conventions

RequestAPI Body: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:

  • 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

Base
FieldEnvironment Type RequiredValidationURL
emailDevelopment stringYesRFC-like regex, uniquehttp://localhost:4000/v1
passwordStaging stringYesMin 8 chars, must contain letters + digitshttps://api-staging.{{domain.com}}/v1
firstNamestringYesProduction validateName()https://api.{{domain.com}}/v1 — 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

SuccessMethod: 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.2 Response Headers

HeaderDescription
Content-Typeapplication/json; charset=utf-8
X-Request-IDEcho of client request ID (201)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 OK:

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

Error Responses:responses:

Status Code Condition
400bad_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
File401 app/api/auth/login/route.tsINVALID_CREDENTIALS
AuthNone
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_requestMissingWrong email or password
401unauthorizedInvalid credentials
429 rate_limitedRATE_LIMITED Too> many5 requestsfailed attempts in 15 min

GET

POST /api/auth/merefresh

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 Users

Get current authenticated user with bank accounts.Endpoints:

FieldMethod SourcePathDescriptionAuth
FileGET app/api/auth/me/route.ts/usersList users (paginated)Admin
AuthGET Required/users/:idGet user by IDSelf or Admin
POST/usersCreate userAdmin
PATCH/users/:idUpdate user fieldsSelf or Admin
DELETE/users/:idDelete user (cookie)soft)Admin
GET/users/meGet current userAuthenticated
PATCH/users/meUpdate current userAuthenticated

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

GET /api/auth/logout

users

LogoutList users with pagination and revokefiltering.

all

Auth sessions.required: Admin

Query parameters:

FieldParameter SourceTypeDefaultDescription
Filepageinteger app/api/auth/logout/route.ts1Page number
pageSizeinteger25Items per page (max: 100)
searchstringSearch name or email (min 2 chars)
rolestringFilter by role: admin, user, viewer
Authstatus 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).

FieldSource
Filestring app/api/auth/refresh/route.tsactiveFilter by status: active, inactive, all
Authsort Required (cookie)

Success Response (200):

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

Transactions

GET /api/transactions

List user's transactions with pagination and filtering.

FieldSource
Filestring app/api/transactions/route.tscreatedAtSort field
AuthRequired

Query Parameters:

ParamTypeDefaultNotes
pageint1Min 1
limitint20Min 1, Max 50
typedir string -desc remittanceSort ordirection: qr_payment
statusstring-processingasc, completed, or faileddesc

Success Response (200)200 OK:

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

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


GET /api/transactions/[id]

users/:id

GetAuth singlerequired: transactionSelf detailsor withAdmin

exchange

Path rate info.parameters:

FieldParameter SourceTypeDescription
Fileid app/api/transactions/[id]/route.ts
AuthUUID RequiredUser ID

Success Response (200)200 OK:

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

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_.usr_01HX7...",
  "email": "..."[email protected]",
  "first_name"name": "...",Jane "last_name": "...",
      "phone": "+47...",
      "date_of_birth": "1995-03-15",
      "kyc_status": "approved"Doe",
  "role": "user",
  "created_at"status": "..."
    }active",
  "transactions": [ {...}, {...} ],
    "recipients": [ {...}, {...} ],
    "bankAccounts": [ {...} ],
    "settings"profile": {
    "currency"avatarUrl": "NOK", "language": "nb", https://cdn.domain.com/avatars/... },
    "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 /api/consents

List user's GDPR consents.

FieldSource
Fileapp/api/consents/route.ts
AuthRequired

Success Response (200):

{
  "data": [
    {
      "id": "con_...",
    "user_id"bio": "usr_..."Software developer"
  },
  "consent_type"createdAt": "terms"2024-01-15T10:30:00Z",
  "granted": 1,
      "granted_at"updatedAt": "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"
  }2024-01-15T10:30:00Z"
}

Error 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:responses:

ornotownedby
Status Code Condition
404 not_foundNOT_FOUND TransactionUser does not foundexist
403 FORBIDDENNon-admin accessing another user

Health

POST Check

/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 /api/healthusers/:id)


PATCH /users/:id

Auth required: Self or Admin

Request body (all fields optional):

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

Response 200 OK: Updated user object.


DELETE /users/:id

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

Auth required: Admin

Response 204 No Content


6.3 {{RESOURCE_NAME}}

System health check (no auth required).Endpoints:

FieldMethod SourcePathDescriptionAuth
FileGET app/api/health/route.ts/{{resource}}List {{resource}}{{Auth level}}
AuthGET None/{{resource}}/:idGet by ID{{Auth level}}
POST/{{resource}}Create{{Auth level}}
PATCH/{{resource}}/:idUpdate{{Auth level}}
DELETE/{{resource}}/:idDelete{{Auth level}}

SuccessTODO: 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 pagination (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(200)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):

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

Returns


503

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 status:token)
curl -X GET "error"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 database(!response.ok) is{
  unreachable.

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

RoleNameDateSignature
Author
Backend Lead
Tech Lead
Product Owner