To use Dext (formerly Receipt Bank) with V0, generate an expense upload UI in V0, then create a Next.js API route at app/api/dext/route.ts that calls the Dext API to submit receipts and invoices for automated OCR extraction. Store your Dext API key in Vercel Dashboard → Settings → Environment Variables as DEXT_API_KEY. Dext processes documents asynchronously, so poll the items endpoint for extracted data.
Automating Expense Processing in Your V0 App with Dext
Dext (previously known as Receipt Bank) sits at the heart of modern bookkeeping workflows for small businesses and accountants. It ingests photos of receipts, scanned invoices, and PDF bills and returns structured data — supplier name, total, VAT, date, category — ready to sync into accounting software. For founders building internal finance tools, client portals, or expense management dashboards with V0, connecting to Dext via API means you can automate the tedious receipt-data-entry step entirely.
The integration pattern follows V0's standard API route model: V0 generates the front-end file upload component and expense data display, while a Next.js API route at app/api/dext handles the Dext API calls server-side. The Dext REST API accepts document uploads as multipart form data, stores them in a processing queue, and returns item IDs. Because OCR extraction is asynchronous — Dext processes documents in the background — your API route needs to handle both document submission (POST) and data retrieval (GET) as separate operations.
One V0-specific consideration: V0 generates React components that run in the browser, so file handling uses standard browser File APIs and FormData. The Next.js API route receives the uploaded file, reconstructs it as a server-side Blob or Buffer, and forwards it to Dext in the correct multipart format. This server-side proxying pattern is essential because Dext's API requires authentication headers that must not be exposed to the browser.
Integration method
V0 generates the file upload and expense dashboard UI components, while a Next.js API route handles multipart document submission to the Dext API. The API route keeps your Dext API credentials server-side and acts as the secure proxy between the browser and Dext's document processing pipeline. Extracted expense data is then fetched from the Dext items endpoint and displayed back in your V0-generated dashboard.
Prerequisites
- A V0 account with a Next.js project at v0.dev
- A Dext account at dext.com with API access enabled (requires a paid Dext plan)
- Your Dext API key from Dext Dashboard → Settings → Integrations → API
- A Vercel account with your V0 project deployed via GitHub
- Basic familiarity with file upload handling in web apps
Step-by-step guide
Generate the Receipt Upload UI in V0
Generate the Receipt Upload UI in V0
Start by prompting V0 to build the front-end interface for document submission. You need two main UI elements: a file upload component and an expense list or dashboard to display the results. V0 is well suited for building polished file upload interfaces with drag-and-drop support and a fallback file picker button — both are standard patterns in Tailwind CSS and React. When prompting V0, specify that the upload button should POST to /api/dext/upload with the file as multipart form data. Critically, the frontend should use FormData to send the file rather than JSON encoding — binary file content cannot be sent as JSON. The V0-generated component should build a FormData object, append the file to it, and fetch the API route without setting a Content-Type header (the browser automatically sets the correct multipart/form-data boundary when you omit it). For the expense list section, tell V0 to fetch from /api/dext/items and render the returned data in a table or card grid. Include a refresh button or auto-polling every 10-15 seconds so users see when Dext finishes processing their document. V0 can generate the polling logic using a useEffect hook with setInterval — ask it to fetch the items list every 15 seconds and stop once the item status changes from 'processing' to 'completed'. Review V0's generated fetch call to confirm it sends FormData correctly. The fetch should look like: const formData = new FormData(); formData.append('file', file); fetch('/api/dext/upload', { method: 'POST', body: formData }). If V0 adds a Content-Type header manually, remove it — the browser must set this automatically to include the multipart boundary.
Create an expense upload page with a drag-and-drop file zone that accepts JPG, PNG, and PDF files up to 10MB. When a file is dropped or selected, show a preview thumbnail and a Submit button. The Submit button should POST the file as FormData to /api/dext/upload and show a loading spinner during upload. Below the upload zone, render a table with columns: Supplier, Date, Amount, Currency, Category, Status. Fetch data from /api/dext/items every 15 seconds to refresh the table.
Paste this in V0 chat
Pro tip: Ask V0 to add file type validation in the component — check that file.type is one of 'image/jpeg', 'image/png', 'image/gif', or 'application/pdf' before submitting. Dext supports these formats and will reject others.
Expected result: V0 generates a file upload interface with drag-and-drop and a data table. The component uses FormData for uploads and polls /api/dext/items for processed results.
Create the Document Upload API Route
Create the Document Upload API Route
Create the server-side API route that receives the file from the browser and forwards it to Dext's document ingestion endpoint. In your project's file structure, create app/api/dext/upload/route.ts. This route runs as a Vercel serverless function, meaning it has access to your Dext API key via process.env and its code is never exposed to the browser. The Dext API accepts document uploads at POST https://api.dext.com/v1/items with the file as multipart form data. The request must include an Authorization header with your API key in the format Bearer {DEXT_API_KEY}. You also need to set the correct supplier context — Dext organizes documents by client or user, so if you have a multi-tenant app, you will need to include the relevant inbox or client identifier in the request. In the Next.js App Router, handling incoming multipart form data uses request.formData(). This gives you a FormData object from which you can extract the file using formData.get('file'). The returned value is a File or Blob object. To forward this to Dext, construct a new FormData with the file appended, then pass that FormData as the body to your fetch call to the Dext API. Handle errors carefully in this route. Dext returns specific HTTP status codes: 201 for successful document submission, 400 for invalid file types, 401 for authentication failures, and 422 for malformed requests. Return meaningful error responses to the client so the V0-generated UI can display helpful messages instead of a generic 'Something went wrong' alert. For larger files or slow connections, be aware that Vercel's default serverless function timeout is 10 seconds on the Hobby plan. Dext document uploads typically complete quickly, but if you experience timeouts on large PDF files, consider upgrading to a Pro plan for the longer 60-second limit or compress images before upload on the client side.
Create a Next.js API route at app/api/dext/upload/route.ts that accepts POST requests with multipart form data. Extract the file from the request using request.formData(), forward it to the Dext API at https://api.dext.com/v1/items using the DEXT_API_KEY environment variable in an Authorization Bearer header, and return the Dext item ID in the response JSON. Handle 400 and 401 errors from Dext and return appropriate error messages.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23export async function POST(request: NextRequest) {4 try {5 const formData = await request.formData();6 const file = formData.get('file') as File | null;78 if (!file) {9 return NextResponse.json(10 { error: 'No file provided' },11 { status: 400 }12 );13 }1415 // Validate file type16 const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];17 if (!allowedTypes.includes(file.type)) {18 return NextResponse.json(19 { error: 'Invalid file type. Accepted: JPEG, PNG, GIF, PDF' },20 { status: 400 }21 );22 }2324 // Forward to Dext API25 const dextFormData = new FormData();26 dextFormData.append('file', file);2728 const response = await fetch('https://api.dext.com/v1/items', {29 method: 'POST',30 headers: {31 Authorization: `Bearer ${process.env.DEXT_API_KEY}`,32 },33 body: dextFormData,34 });3536 if (!response.ok) {37 const errorText = await response.text();38 console.error('Dext API error:', response.status, errorText);39 return NextResponse.json(40 { error: `Dext API error: ${response.status}` },41 { status: response.status }42 );43 }4445 const data = await response.json();46 return NextResponse.json({ itemId: data.id, status: data.status });47 } catch (error) {48 console.error('Upload error:', error);49 return NextResponse.json(50 { error: 'Failed to upload document' },51 { status: 500 }52 );53 }54}Pro tip: Dext processes documents asynchronously. The upload endpoint returns an item ID immediately; the extracted data becomes available via the items endpoint after a short delay (typically 30 seconds to 2 minutes depending on document complexity).
Expected result: Uploading a receipt JPEG or PDF from the browser calls this API route, which forwards the document to Dext and returns an item ID. The browser can then poll the items endpoint using this ID.
Create the Items Retrieval API Route
Create the Items Retrieval API Route
Create a second API route to fetch processed expense data from Dext. Create app/api/dext/items/route.ts for fetching the full list of items, and optionally app/api/dext/items/[id]/route.ts for fetching a single item by ID. The items endpoint is what the V0-generated dashboard polls to show extracted receipt data. The Dext GET /v1/items endpoint returns an array of item objects. Each item includes fields like id, status (processing or completed), supplier_name, date, total, currency, tax, category, and a thumbnail_url. The status field is crucial for polling — only when status is completed will the extracted fields like supplier_name and total be populated. Items with status processing will have these fields as null or empty strings. For the single-item route (app/api/dext/items/[id]/route.ts), extract the id from the route params using the params argument passed to the GET function. Fetch from https://api.dext.com/v1/items/{id} and return the full item data. This is useful for the three-step invoice review flow where the user uploads a document, waits for processing, then reviews the extracted data in a pre-filled form. Consider adding pagination support to the items list endpoint. Dext's API supports page and per_page query parameters. For an expense dashboard that might have hundreds of items, you do not want to fetch everything at once. Accept page and limit as query parameters in your Next.js route, forward them to Dext, and include pagination metadata in your response so the V0-generated UI can show 'Load more' or page number controls. For production deployments, consider caching the items response in Vercel's data cache using Next.js fetch caching with revalidate: 30 to reduce the number of Dext API calls from your polling frontend, while still keeping data reasonably fresh.
Create a Next.js API route at app/api/dext/items/route.ts that fetches all items from the Dext API at https://api.dext.com/v1/items using DEXT_API_KEY in an Authorization Bearer header. Return the array of items as JSON. Also create app/api/dext/items/[id]/route.ts that fetches a single item by ID from https://api.dext.com/v1/items/{id} and returns the item data including supplier_name, date, total, currency, and status fields.
Paste this in V0 chat
1import { NextRequest, NextResponse } from 'next/server';23export async function GET(request: NextRequest) {4 try {5 const { searchParams } = new URL(request.url);6 const page = searchParams.get('page') || '1';7 const perPage = searchParams.get('per_page') || '25';89 const response = await fetch(10 `https://api.dext.com/v1/items?page=${page}&per_page=${perPage}`,11 {12 headers: {13 Authorization: `Bearer ${process.env.DEXT_API_KEY}`,14 'Content-Type': 'application/json',15 },16 // Revalidate every 30 seconds to reduce API calls17 next: { revalidate: 30 },18 }19 );2021 if (!response.ok) {22 return NextResponse.json(23 { error: `Dext API error: ${response.status}` },24 { status: response.status }25 );26 }2728 const data = await response.json();29 return NextResponse.json({30 items: data.items || data,31 total: data.total,32 page: parseInt(page),33 });34 } catch (error) {35 console.error('Items fetch error:', error);36 return NextResponse.json(37 { error: 'Failed to fetch items' },38 { status: 500 }39 );40 }41}Pro tip: Add a status query parameter filter so the frontend can request only completed items: ?status=completed. Forward this to Dext's API as a query param to avoid returning items that are still being processed.
Expected result: The dashboard frontend can call GET /api/dext/items and receive a list of expense items with extracted data. Items with status 'processing' appear in the list but have empty supplier/amount fields until Dext completes extraction.
Add Environment Variables in Vercel
Add Environment Variables in Vercel
Your API routes read DEXT_API_KEY from environment variables. You need to add this to your Vercel project so it is available when your serverless functions run in production, and optionally create a .env.local file for local development. Go to your Vercel Dashboard and open your project. Click the Settings tab, then Environment Variables in the left sidebar. Add the following variable: DEXT_API_KEY with the value of your Dext API key, which you can find in Dext Dashboard → Settings → Integrations → API. Set the environment to Production, Preview, and Development so it is available across all deployments. Important: do NOT add the NEXT_PUBLIC_ prefix to DEXT_API_KEY. This is a server-only secret. Adding the NEXT_PUBLIC_ prefix would cause Next.js to embed this value directly in the compiled JavaScript bundle that is sent to every browser visitor — completely exposing your API key. The key should only exist in server-side code within your API routes, which run as Vercel serverless functions invisible to the browser. For local development, create a .env.local file in your project root with the same variable. This file is included in .gitignore by default in V0-generated projects, so it will not be committed to GitHub. When you run npm run dev, Next.js automatically loads variables from .env.local, making your local API routes work the same way as production. After adding the environment variable in Vercel, you must trigger a redeployment for the change to take effect. The easiest way is to push any small commit to your GitHub repository — Vercel auto-deploys on every push. Alternatively, in your Vercel project dashboard, go to Deployments, find your latest deployment, click the three-dot menu, and select Redeploy.
1# .env.local (for local development only — do NOT commit to git)2DEXT_API_KEY=your_dext_api_key_herePro tip: To verify the environment variable is loading correctly, add a temporary console.log at the top of your API route: console.log('Dext key loaded:', !!process.env.DEXT_API_KEY). Check Vercel Function logs to see 'true'. Remove the log after confirming.
Expected result: Vercel Dashboard shows DEXT_API_KEY saved under Environment Variables. After redeployment, your API routes can access process.env.DEXT_API_KEY without the value being undefined.
Test the Full Receipt Processing Flow
Test the Full Receipt Processing Flow
Test the complete flow from receipt upload to extracted data display. Start with a clear, well-lit receipt photograph or a simple PDF invoice — Dext's OCR accuracy depends on image quality, so using a sharp, properly oriented image gives the most predictable results for initial testing. Navigate to your deployed V0 app and use the file upload UI to submit a test receipt. After clicking upload, your app should show a loading state while the file travels through your Next.js API route to Dext. If the upload succeeds, your API route returns an item ID which the UI can use to poll for the result. Open Vercel Dashboard → your project → Functions → View Logs in real time as you upload. You should see the POST request to /api/dext/upload appear in the logs. If the upload fails, the log will show the HTTP status code and error message returned from Dext's API. Common issues at this stage are authentication errors (wrong API key) or file format issues (Dext rejects certain HEIC or TIFF formats from iPhone cameras — convert to JPEG first). After a successful upload, wait 30-90 seconds and refresh the expense table. The item should appear with status 'processing' initially, then transition to 'completed' with extracted fields like supplier name, date, and total amount populated. If the status remains 'processing' for more than 5 minutes, check Dext's own dashboard to see if there is a processing error on their end. For complex receipt management workflows with approval chains, budget tracking, or multi-currency handling, RapidDev's team can help architect the integration and connect extracted Dext data to your accounting software or database schema.
Add a status indicator to the expense table that shows a yellow 'Processing' badge for items with status 'processing' and a green 'Ready' badge for items with status 'completed'. Auto-refresh the table every 15 seconds. Show a toast notification when a processing item transitions to completed.
Paste this in V0 chat
Pro tip: Dext's sandbox or test environment (if available on your plan) lets you submit test documents without consuming your production processing quota. Check your Dext account settings for a test API key.
Expected result: Uploading a receipt JPEG successfully processes through the API route to Dext, appears in the expense table with a Processing status, and updates to show extracted supplier name, date, and amount within 1-2 minutes.
Common use cases
Employee Expense Submission Portal
A startup builds an internal expense portal where employees photograph receipts on mobile and upload them directly from the browser. The V0-generated interface shows a drag-and-drop upload area and a running list of submitted expenses with processing status. Once Dext extracts the data, the portal displays line items for manager review and approval.
Create an expense submission page with a file upload area that accepts images and PDFs, a form field for the employee name and expense category, and a table below showing submitted expenses with columns for Date, Supplier, Amount, and Status. The upload button should POST to /api/dext/upload.
Copy this prompt to try it in V0
Client Accounting Dashboard
An accounting firm builds a client-facing portal where clients upload invoices and receipts. The dashboard fetches all processed items from Dext via the API and displays them in a categorized list with extraction confidence scores. Clients can flag items that need correction before they are synced to their accounting software.
Build a receipt processing dashboard that shows a grid of expense cards. Each card displays a receipt thumbnail, extracted supplier name, date, total amount with currency, and a category badge. Add a filter bar at the top for date range and category. Fetch data from /api/dext/items.
Copy this prompt to try it in V0
Automated Invoice Processing for SaaS
A SaaS product integrates Dext to let users forward supplier invoices into their account by uploading PDFs. The V0-generated upload flow accepts PDF files, submits them to Dext via the API route, and shows a processing animation while Dext extracts the invoice details. Once complete, the extracted data populates a review form that the user can confirm before saving to their database.
Create an invoice upload flow with three steps: Step 1 shows a PDF drop zone with a file browser button. Step 2 shows a loading animation with the message 'Extracting invoice data...'. Step 3 shows a pre-filled form with fields for Supplier, Invoice Number, Date, Amount, and Tax. Include Back and Confirm buttons. Call /api/dext/upload on Step 1 and /api/dext/items/{id} to populate Step 3.
Copy this prompt to try it in V0
Troubleshooting
API route returns 401 Unauthorized when uploading to Dext
Cause: The DEXT_API_KEY environment variable is missing, incorrectly named, or the key has been revoked in the Dext Dashboard.
Solution: Go to Vercel Dashboard → Settings → Environment Variables and verify DEXT_API_KEY exists and matches your key from Dext Dashboard → Settings → Integrations → API. After updating the variable, trigger a redeployment. For local testing, check your .env.local file.
File upload fails with 'Failed to parse multipart form data' or the file arrives at the API route as null
Cause: The browser-side fetch call is setting a manual Content-Type header like 'multipart/form-data' without the required boundary parameter, which prevents the server from parsing the body. Or the frontend is sending JSON instead of FormData.
Solution: Remove any manual Content-Type header from the fetch call when sending FormData. The browser automatically sets 'multipart/form-data; boundary=...' when you pass a FormData object as the body — manually setting it strips the boundary and breaks parsing.
1// WRONG — manually setting Content-Type breaks multipart parsing2fetch('/api/dext/upload', {3 method: 'POST',4 headers: { 'Content-Type': 'multipart/form-data' },5 body: formData,6});78// CORRECT — omit Content-Type, let browser set it with boundary9fetch('/api/dext/upload', {10 method: 'POST',11 body: formData,12});Uploaded items appear in Dext Dashboard but extracted fields (supplier_name, total) are always empty
Cause: You are reading item data immediately after upload before Dext has finished OCR processing. The item exists in Dext's system with status 'processing' but the extraction pipeline has not completed yet.
Solution: Check the status field on each item before displaying extracted data. Only render supplier_name, total, and other extracted fields when status is 'completed'. Implement polling in your frontend — fetch /api/dext/items every 15-30 seconds until the target item's status changes from 'processing' to 'completed'.
File upload works locally but returns 413 Payload Too Large on Vercel
Cause: Vercel's serverless functions have a 4.5MB request body limit. Large receipt photos from modern smartphone cameras can exceed this limit.
Solution: Add client-side image compression before upload. Use the browser-image-compression npm package to resize images to a maximum of 1024px and compress to under 1MB before appending to FormData. PDF files may need to be processed differently — consider limiting PDF size to 4MB on the client with a file size check.
1// Install: npm install browser-image-compression2import imageCompression from 'browser-image-compression';34const options = { maxSizeMB: 1, maxWidthOrHeight: 1024 };5const compressedFile = await imageCompression(originalFile, options);6formData.append('file', compressedFile);Best practices
- Always validate file type and size on the client before upload to give users immediate feedback and prevent unnecessary API calls.
- Store DEXT_API_KEY without any NEXT_PUBLIC_ prefix — this is a server-only secret that must never appear in browser JavaScript.
- Implement polling with exponential backoff rather than fixed intervals — start at 10 seconds, double to 20, 40, then cap at 60 seconds to reduce API load.
- Handle the Dext 'processing' status gracefully in the UI — show a loading skeleton or spinner for items that are not yet complete rather than blank fields.
- Log the Dext item ID immediately after upload so you can debug specific documents that fail to extract correctly using the Dext Dashboard's item view.
- Compress and resize receipt images on the client before upload to stay within Vercel's 4.5MB serverless body limit and speed up Dext processing.
- Never trust client-reported file types — always validate MIME type server-side in the API route before forwarding to Dext.
- For multi-user apps, associate each Dext item ID with the uploading user in your own database so you can scope the items list to the current user.
Alternatives
AWS S3 is an alternative if you only need file storage without OCR data extraction — pair it with AWS Textract for document parsing if you want to build your own extraction pipeline.
Airtable is an alternative if you want a simple spreadsheet-style expense tracker without automated OCR — users enter expense data manually into an Airtable base synced to your V0 app.
Typeform is an alternative if you want a structured manual expense submission form rather than automated receipt scanning, using Typeform's file upload question type.
Frequently asked questions
Does Dext have a public API I can use with V0?
Yes, Dext provides a REST API for submitting documents and retrieving extracted data. API access is available on paid Dext plans. You can find your API key in the Dext Dashboard under Settings → Integrations → API. The API supports document upload, item retrieval, and webhook notifications for processing completion.
How long does Dext take to extract data from a receipt?
Dext typically processes receipts and invoices within 30 seconds to 2 minutes for clear images. Complex multi-page PDFs or low-quality photographs may take longer. Dext does not guarantee a specific processing time — always build your integration around polling the item status rather than assuming data will be available immediately after upload.
Can V0 generate code to connect Dext with QuickBooks automatically?
V0 can generate the Dext API integration code and the QuickBooks API integration code as separate API routes. However, you would need to connect them in your own Next.js server action or API route — fetch the extracted data from Dext and then POST it to QuickBooks as an expense or bill. V0 can scaffold this pipeline if you describe the complete flow in your prompt.
What file formats does Dext accept?
Dext accepts JPEG, PNG, GIF, TIFF, BMP, and PDF files. The most reliable formats for OCR accuracy are JPEG and PDF. Note that HEIC files from iPhone cameras are not always supported — prompt users to enable JPEG capture in their iPhone Camera settings, or convert HEIC to JPEG client-side before upload.
How do I handle multiple users submitting receipts without mixing up their documents?
Dext organizes documents by inboxes or client accounts depending on your plan type. For a multi-user app, create a separate Dext client or inbox for each user and pass the appropriate inbox identifier in your API request. Alternatively, store the Dext item ID alongside the user ID in your own database immediately after upload, then filter the items list by user when displaying data.
Why does my Vercel deployment fail to upload files even though local development works?
The most common cause is that your .env.local file works locally but the DEXT_API_KEY variable was not added to Vercel's Environment Variables. Vercel does not read .env.local files during deployment — all production environment variables must be added through Vercel Dashboard → Settings → Environment Variables. After adding the variable, trigger a redeployment.
Can I use webhooks instead of polling for Dext processing completion?
Dext supports webhook notifications that fire when an item's processing status changes to completed. Configure the webhook URL in Dext Dashboard → Settings → Integrations → Webhooks, pointing to a new API route in your V0 app at something like /api/dext/webhook. This is more efficient than polling for high-volume applications, though polling is simpler to implement for getting started.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation