{"openapi":"3.1.0","info":{"title":"Nevlo API","version":"1.0.0","description":"Banking data API for connected bank accounts. Provides access to accounts,\ntransactions, cashflow analysis, financial snapshots, and subscription detection.\n","contact":{"url":"https://nevlo.io"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://nevlo.io/api/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"https://nevlo.io/api/oauth/authorize","tokenUrl":"https://nevlo.io/api/oauth/token","refreshUrl":"https://nevlo.io/api/oauth/token","scopes":{"read:accounts":"Read bank accounts and balances","read:transactions":"Read transaction history","read:cashflow":"Read cashflow analysis and subscription detection"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"BankConnectionInfo":{"type":"object","required":["bankName","status"],"properties":{"bankName":{"type":"string","example":"Sparkasse"},"status":{"type":"string","example":"ACTIVE"}}},"Account":{"type":"object","required":["id","currency","bankConnection"],"properties":{"id":{"type":"string"},"accountName":{"type":"string","nullable":true},"iban":{"type":"string","nullable":true},"accountType":{"type":"string","nullable":true},"balance":{"type":"number","nullable":true,"example":2450},"currency":{"type":"string","example":"EUR"},"lastSyncedAt":{"type":"string","format":"date-time","nullable":true},"bankConnection":{"$ref":"#/components/schemas/BankConnectionInfo"}}},"BankAccountRef":{"type":"object","required":["id","bankConnection"],"properties":{"id":{"type":"string"},"iban":{"type":"string","nullable":true},"accountName":{"type":"string","nullable":true},"bankConnection":{"type":"object","required":["bankName"],"properties":{"bankName":{"type":"string"}}}}},"Transaction":{"type":"object","required":["id","amount","currency","bookingDate","bankAccount"],"properties":{"id":{"type":"string"},"amount":{"type":"number","example":-42.5},"currency":{"type":"string","example":"EUR"},"bookingDate":{"type":"string","format":"date-time"},"valueDate":{"type":"string","format":"date-time","nullable":true},"merchantName":{"type":"string","nullable":true},"counterpartName":{"type":"string","nullable":true},"counterpartIban":{"type":"string","nullable":true},"counterpartMandateReference":{"type":"string","nullable":true,"description":"SEPA direct debit mandate reference"},"type":{"type":"string","nullable":true,"enum":["direct_debit","transfer","standing_order","cash_withdrawal","cash_deposit","card_payment","interest","fee","salary","other"],"description":"Normalized transaction type"},"category":{"type":"string","nullable":true},"purpose":{"type":"string","nullable":true},"bankAccount":{"$ref":"#/components/schemas/BankAccountRef"}}},"TransactionDetail":{"description":"Full transaction details including counterpart info and technical codes","allOf":[{"$ref":"#/components/schemas/Transaction"},{"type":"object","properties":{"counterpartBic":{"type":"string","nullable":true},"counterpartBankName":{"type":"string","nullable":true},"counterpartCreditorId":{"type":"string","nullable":true,"description":"SEPA creditor identifier"},"counterpartDebitorId":{"type":"string","nullable":true},"bankTransactionCode":{"type":"string","nullable":true,"description":"ISO 20022 bank transaction code (e.g. PMNT-RCDT-SALA)"},"sepaPurposeCode":{"type":"string","nullable":true,"description":"SEPA purpose code according to ISO 20022"},"isPotentialDuplicate":{"type":"boolean","default":false},"isAdjustingEntry":{"type":"boolean","default":false},"createdAt":{"type":"string","format":"date-time"}}}]},"Pagination":{"type":"object","required":["page","perPage","total","pageCount"],"properties":{"page":{"type":"integer","example":1},"perPage":{"type":"integer","example":50},"total":{"type":"integer","example":142},"pageCount":{"type":"integer","example":3}}},"MonthlyCashflow":{"type":"object","required":["month","income","expenses","net"],"properties":{"month":{"type":"string","pattern":"^\\d{4}-\\d{2}$","example":"2026-01"},"income":{"type":"number","example":3200},"expenses":{"type":"number","example":2100.5},"net":{"type":"number","example":1099.5}}},"SnapshotAccount":{"type":"object","required":["name","bank","balance","currency"],"properties":{"name":{"type":"string","example":"Girokonto"},"bank":{"type":"string","example":"Sparkasse"},"balance":{"type":"number","example":3200.5},"currency":{"type":"string","example":"EUR"}}},"TopCategory":{"type":"object","required":["category","amount","percentage"],"properties":{"category":{"type":"string","example":"RENT"},"amount":{"type":"number","example":750},"percentage":{"type":"integer","example":41}}},"SyncJobSummary":{"type":"object","properties":{"syncId":{"type":"string","nullable":true,"description":"Sync job ID. Null if the connection could not be synced. Use this ID with `GET /sync/{syncId}` to poll for status.","example":"cmxyz456"},"status":{"type":"string","description":"Initial status of the sync job","example":"syncing"},"error":{"type":"string","nullable":true,"description":"Error message if the sync could not be started"}}},"SyncJobStatus":{"type":"object","required":["syncId","status","createdAt"],"properties":{"syncId":{"type":"string","example":"cmxyz456"},"status":{"type":"string","enum":["pending","syncing","importing","completed","failed"],"description":"- `pending` — Job created, not yet started\n- `syncing` — Waiting for bank data from finAPI\n- `importing` — Bank data received, importing transactions\n- `completed` — Sync finished successfully\n- `failed` — Sync failed (see error field)\n"},"newTransactions":{"type":"integer","description":"Number of new transactions imported (only set when completed)","example":12},"error":{"type":"string","nullable":true,"description":"Error details (only set when failed)"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"DetectedSubscription":{"type":"object","required":["merchantName","amount","currency","frequency","lastCharged","occurrences"],"properties":{"merchantName":{"type":"string","example":"Netflix"},"amount":{"type":"number","example":12.99},"currency":{"type":"string","example":"EUR"},"frequency":{"type":"string","enum":["weekly","biweekly","monthly","quarterly","yearly"]},"lastCharged":{"type":"string","format":"date-time"},"occurrences":{"type":"integer","example":6}}}},"parameters":{"accountIds":{"name":"accountIds","in":"query","description":"Comma-separated bank account IDs. Optional if user has exactly one account.","schema":{"type":"string"},"example":"clx1abc,clx2def"}},"responses":{"Unauthorized":{"description":"Missing or invalid access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Unauthorized"}}}},"Forbidden":{"description":"Insufficient scope or no active subscription","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"insufficientScope":{"value":{"error":"Insufficient scope. Required: read:accounts"}},"noSubscription":{"value":{"error":"Active subscription required. Subscribe at https://nevlo.io/dashboard/billing"}}}}}},"RateLimited":{"description":"Rate limit exceeded (60 requests/minute)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Rate limit exceeded"}}}},"InternalError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Internal error"}}}}}},"x-rate-limit":{"max":60,"windowSeconds":60,"scope":"per-IP"},"paths":{"/accounts":{"get":{"operationId":"getAccounts","summary":"List connected bank accounts","description":"Returns all bank accounts connected to the authenticated user with current balances.","tags":["Accounts"],"security":[{"bearerAuth":["read:accounts"]}],"responses":{"200":{"description":"List of bank accounts","content":{"application/json":{"schema":{"type":"object","required":["accounts"],"properties":{"accounts":{"type":"array","items":{"$ref":"#/components/schemas/Account"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/transactions":{"get":{"operationId":"getTransactions","summary":"List transactions","description":"Returns paginated transactions with optional filters for date range, category, and text search.","tags":["Transactions"],"security":[{"bearerAuth":["read:transactions"]}],"parameters":[{"$ref":"#/components/parameters/accountIds"},{"name":"category","in":"query","description":"Filter by transaction category","schema":{"type":"string","enum":["GROCERIES","RENT","SALARY","TRANSPORT","SUBSCRIPTION","ENTERTAINMENT","TRANSFER","INSURANCE","HEALTH","DINING","SHOPPING","SAVINGS","OTHER"]}},{"name":"dateFrom","in":"query","description":"Start date (ISO 8601)","schema":{"type":"string","format":"date"},"example":"2026-01-01"},{"name":"dateTo","in":"query","description":"End date (ISO 8601)","schema":{"type":"string","format":"date"},"example":"2026-03-31"},{"name":"search","in":"query","description":"Text search across merchant name, counterpart name, and purpose","schema":{"type":"string"}},{"name":"page","in":"query","description":"Page number (default 1)","schema":{"type":"integer","minimum":1,"default":1}},{"name":"perPage","in":"query","description":"Results per page (default 50, max 100)","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Paginated transaction list","content":{"application/json":{"schema":{"type":"object","required":["transactions","pagination"],"properties":{"transactions":{"type":"array","items":{"$ref":"#/components/schemas/Transaction"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"400":{"description":"Bad request (e.g., multiple accounts without specifying accountIds)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/transactions/{id}":{"get":{"operationId":"getTransaction","summary":"Get a single transaction","description":"Returns full details of a single transaction including counterpart info, mandate reference, and technical codes.","tags":["Transactions"],"security":[{"bearerAuth":["read:transactions"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Transaction ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Transaction details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionDetail"},"example":{"id":"clx1abc123","amount":-42.5,"currency":"EUR","bookingDate":"2026-03-15T00:00:00.000Z","valueDate":"2026-03-15T00:00:00.000Z","merchantName":"REWE","counterpartName":"REWE Markt GmbH","counterpartIban":"DE89370400440532013000","counterpartBic":"COBADEFFXXX","counterpartBankName":"Commerzbank","counterpartMandateReference":"MR-2026-001","counterpartCreditorId":"DE98ZZZ09999999999","counterpartDebitorId":null,"type":"card_payment","category":"GROCERIES","purpose":"Einkauf REWE Markt","bankTransactionCode":"PMNT-CCRD-POSD","sepaPurposeCode":null,"isPotentialDuplicate":false,"isAdjustingEntry":false,"createdAt":"2026-03-15T10:30:00.000Z","bankAccount":{"id":"clx2def456","iban":"DE27100777770209299700","accountName":"Girokonto","bankConnection":{"bankName":"Sparkasse"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Transaction not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"Transaction not found"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/cashflow":{"get":{"operationId":"getCashflow","summary":"Monthly cashflow summary","description":"Returns monthly income, expenses, and net cashflow for the specified number of months.","tags":["Cashflow"],"security":[{"bearerAuth":["read:cashflow"]}],"parameters":[{"$ref":"#/components/parameters/accountIds"},{"name":"months","in":"query","description":"Number of months of history (default 6)","schema":{"type":"integer","minimum":1,"default":6}}],"responses":{"200":{"description":"Monthly cashflow breakdown","content":{"application/json":{"schema":{"type":"object","required":["cashflow"],"properties":{"cashflow":{"type":"array","items":{"$ref":"#/components/schemas/MonthlyCashflow"}}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/snapshot":{"get":{"operationId":"getSnapshot","summary":"Financial snapshot","description":"Returns a financial overview including total balance, account list,\nand top expense categories from the last 30 days.\n","tags":["Snapshot"],"security":[{"bearerAuth":["read:accounts"]}],"parameters":[{"$ref":"#/components/parameters/accountIds"}],"responses":{"200":{"description":"Financial snapshot","content":{"application/json":{"schema":{"type":"object","required":["totalBalance","accountCount","accounts","last30Days"],"properties":{"totalBalance":{"type":"number","example":4200.5},"accountCount":{"type":"integer","example":2},"accounts":{"type":"array","items":{"$ref":"#/components/schemas/SnapshotAccount"}},"last30Days":{"type":"object","required":["totalExpenses","topCategories"],"properties":{"totalExpenses":{"type":"number","example":1850},"topCategories":{"type":"array","items":{"$ref":"#/components/schemas/TopCategory"}}}}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/subscriptions":{"get":{"operationId":"getSubscriptions","summary":"Detected recurring payments","description":"Analyzes transaction history (last 6 months) to detect recurring payments\nsuch as subscriptions. Uses heuristic frequency detection.\n","tags":["Subscriptions"],"security":[{"bearerAuth":["read:cashflow"]}],"parameters":[{"$ref":"#/components/parameters/accountIds"}],"responses":{"200":{"description":"Detected subscriptions","content":{"application/json":{"schema":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/DetectedSubscription"}}}}}}},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/sync":{"post":{"operationId":"triggerSync","summary":"Trigger bank data sync","description":"Triggers a fresh bank data sync. By default, all connected bank accounts are synced.\nPass `accountIds` to limit the sync to specific accounts.\n\nThe sync runs asynchronously — use the returned `syncId` with `GET /sync/{syncId}` to poll for status.\n\nRate limited to once per bank connection every 30 minutes.\n","tags":["Sync"],"security":[{"bearerAuth":["read:accounts"]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"accountIds":{"type":"array","items":{"type":"string"},"description":"Bank account IDs to sync. Only the bank connections containing these accounts will be refreshed.\nIf omitted, all bank connections are synced.\nUse `GET /accounts` to find available account IDs.\nMaximum 50 IDs per request.\n","example":["clxyz123"]}}}}}},"responses":{"202":{"description":"Sync jobs initiated","content":{"application/json":{"schema":{"type":"object","required":["syncJobs","skippedConnections"],"properties":{"syncJobs":{"type":"array","items":{"$ref":"#/components/schemas/SyncJobSummary"}},"skippedConnections":{"type":"integer","description":"Number of connections skipped due to rate limiting"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No bank connections found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"All connections were synced recently","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"$ref":"#/components/responses/InternalError"}}}},"/sync/{syncId}":{"get":{"operationId":"getSyncStatus","summary":"Get sync job status","description":"Returns the current status of a sync job. When the bank update completes,\nthis endpoint automatically imports new transactions and returns the final result.\n\nStatus flow: `pending` → `syncing` → `importing` → `completed` or `failed`\n","tags":["Sync"],"security":[{"bearerAuth":["read:accounts"]}],"parameters":[{"name":"syncId","in":"path","required":true,"schema":{"type":"string"},"description":"Sync job ID returned by POST /sync"}],"responses":{"200":{"description":"Sync job status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncJobStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Sync job not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"$ref":"#/components/responses/InternalError"}}}}}}