API Reference
v1.0.0Automate invoicing, file delivery, and billing workflows with the Involok REST API. All endpoints return JSON and follow a consistent { data } / { error } envelope.
Authentication
All API requests require an API key passed as a Bearer token. Generate keys in Settings → API Keys.
Authorization: Bearer inv_live_<your_key>API access requirements
- API access requires an Activated (Plus) or Pro plan — connect Stripe to unlock
- Keys are shown once on creation — store them securely
- Revoked keys return
401 Unauthorizedimmediately
Available scopes
invoices:readRead invoices, line items, and Lokbox productsinvoices:writeCreate, send, void invoices, and manage Lokbox productsfiles:readRead file metadatafiles:writeUpload and delete filesfull_accessBypass all individual scope checksErrors
Failed requests return a consistent error shape with an HTTP status code.
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key.",
"status": 401
}
}400Bad request — malformed JSON or missing required fields401Unauthorized — API key missing, invalid, or revoked403Forbidden — key lacks the required scope, or plan doesn't support API404Not found — resource doesn't exist or belongs to another user422Validation error — request body failed schema validation429Rate limited — 100 requests per 60 seconds per key500Internal server errorInvoices
Create and manage invoices, send them to recipients, and track payment status.
/api/v1/invoicesReturns a paginated list of invoices belonging to the authenticated user.
Parameters
statusstringFilter by status: draft | sent | paid | overdue | voidedlimitnumberMax results to return (default 20, max 100)Example request
curl 'https://involok.vercel.app
/api/v1/invoices' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": [
{
"id": "inv_01abc",
"invoice_number": "F2BB3F9E-0001",
"recipient_name": "Jordan Lee",
"recipient_email": "jordan@example.com",
"status": "sent",
"currency": "usd",
"due_date": "2026-04-01",
"created_at": "2026-03-20T12:00:00Z"
}
]
}/api/v1/invoicesCreates a new invoice. Pass send_immediately: true to send the invoice email in the same request.
Example request
curl -X POST 'https://involok.vercel.app
/api/v1/invoices' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"recipient_name": "Jordan Lee",
"recipient_email": "jordan@example.com",
"line_items": [
{
"description": "Brand identity design",
"quantity": 1,
"unit_amount": 240000
}
],
"due_date": "2026-04-01",
"notes": "Net-30 terms apply.",
"send_immediately": false
}'Example response
{
"data": {
"id": "inv_01abc",
"invoice_number": "F2BB3F9E-0001",
"status": "draft",
"created_at": "2026-03-20T12:00:00Z"
}
}Amounts are in cents (USD). A $2,400 item = unit_amount: 240000.
/api/v1/invoices/:idReturns a single invoice with its line items and attached file.
Parameters
idstringrequiredInvoice IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/invoices/<id>' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"id": "inv_01abc",
"invoice_number": "F2BB3F9E-0001",
"recipient_name": "Jordan Lee",
"status": "paid",
"invoice_line_items": [
{
"description": "Brand identity design",
"quantity": 1,
"unit_amount": 240000,
"total_amount": 240000
}
]
}
}/api/v1/invoices/:id/sendSends the invoice email to the recipient. Invoice must be in draft status.
Parameters
idstringrequiredInvoice IDExample request
# Replace <id> with the actual resource ID
curl -X POST 'https://involok.vercel.app
/api/v1/invoices/<id>/send' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"additional_emails": [
"cc@example.com"
],
"force": false
}'Example response
{
"data": {
"id": "inv_01abc",
"status": "sent",
"sent_at": "2026-03-20T12:05:00Z"
}
}Set force: true to send even if Stripe is not connected (recipient cannot pay).
/api/v1/invoices/:id/pdfReturns the invoice as a PDF file with your brand styling applied.
Parameters
idstringrequiredInvoice IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/invoices/<id>/pdf' \
-H 'Authorization: Bearer inv_live_...'Response Content-Type is application/pdf.
/api/v1/invoices/:idUpdates fields on a draft invoice. Sending line_items replaces all existing items atomically.
Parameters
idstringrequiredInvoice IDExample request
# Replace <id> with the actual resource ID
curl -X PATCH 'https://involok.vercel.app
/api/v1/invoices/<id>' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"recipient_name": "Jordan Lee",
"due_date": "2026-05-01",
"line_items": [
{
"description": "Consulting",
"quantity": 2,
"unit_amount": 15000
}
]
}'Example response
{
"data": {
"id": "inv_01abc",
"status": "draft"
}
}Only draft invoices can be updated.
/api/v1/invoices/:id/mark-paidManually marks an invoice as paid without a Stripe payment. Useful for offline payments.
Parameters
idstringrequiredInvoice IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/invoices/<id>/mark-paid' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"id": "inv_01abc",
"status": "paid",
"paid_at": "2026-03-20T12:10:00Z"
}
}/api/v1/invoices/:id/remindResends the invoice email as a reminder. Only works for sent or overdue invoices.
Parameters
idstringrequiredInvoice IDExample request
# Replace <id> with the actual resource ID
curl -X POST 'https://involok.vercel.app
/api/v1/invoices/<id>/remind' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"message": "Just a friendly reminder that this invoice is due soon."
}'Example response
{
"data": {
"sent": true
}
}/api/v1/invoices/:id/voidCancels an invoice. If already sent, the recipient receives a cancellation email.
Parameters
idstringrequiredInvoice IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/invoices/<id>/void' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"id": "inv_01abc",
"status": "voided",
"voided_at": "2026-03-20T12:10:00Z"
}
}Paid invoices cannot be voided.
/api/v1/invoices/exportDownloads all invoices as a CSV file. Supports optional date range filtering.
Parameters
fromstringStart date (YYYY-MM-DD)tostringEnd date (YYYY-MM-DD)Example request
curl 'https://involok.vercel.app
/api/v1/invoices/export' \
-H 'Authorization: Bearer inv_live_...'Response Content-Type is text/csv.
Files
Upload files to attach to invoices. Files are locked by default and unlock automatically when the invoice is paid.
/api/v1/filesUploads a file to storage. Optionally associate it with an invoice via invoice_id.
Parameters
fileFile (multipart)requiredThe file to uploadinvoice_idstringAssociate with an existing invoiceExample request
curl 'https://involok.vercel.app
/api/v1/files' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"id": "file_01xyz",
"original_name": "project_files.zip",
"size_bytes": 4200000,
"mime_type": "application/zip",
"is_unlocked": false,
"expiry_duration": "7d",
"created_at": "2026-03-20T12:00:00Z"
}
}Request must be multipart/form-data. Invoice file size limits: Free 50MB, Activated 2GB, Pro 10GB. Lokbox product limits: Free 250MB, Activated 5GB, Pro 16GB.
/api/v1/files/:idReturns metadata for a file. Does not return file contents.
Parameters
idstringrequiredFile IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/files/<id>' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"id": "file_01xyz",
"original_name": "project_files.zip",
"is_unlocked": true,
"unlocked_at": "2026-03-20T14:00:00Z",
"access_expires_at": "2026-03-27T14:00:00Z"
}
}/api/v1/files/:idPermanently deletes a file from storage and the database.
Parameters
idstringrequiredFile IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/files/<id>' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"deleted": true
}
}/api/v1/files/:id/download-urlReturns a short-lived signed URL to download the file. File must be unlocked.
Parameters
idstringrequiredFile IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/files/<id>/download-url' \
-H 'Authorization: Bearer inv_live_...'Redirects (302) to the signed URL. URL expires after 1 hour.
Webhooks
Register endpoints to receive real-time event notifications when invoices are paid, files unlock, and more.
/api/v1/webhooksList webhooks
Example request
curl 'https://involok.vercel.app
/api/v1/webhooks' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": [
{
"id": "wh_01abc",
"url": "https://yourapp.com/hooks/involok",
"events": [
"invoice.paid",
"file.unlocked"
],
"created_at": "2026-03-20T12:00:00Z"
}
]
}/api/v1/webhooksRegisters a URL to receive event payloads. Returns a signing secret shown once.
Example request
curl -X POST 'https://involok.vercel.app
/api/v1/webhooks' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://yourapp.com/hooks/involok",
"events": [
"invoice.paid",
"invoice.sent",
"file.unlocked"
]
}'Example response
{
"data": {
"id": "wh_01abc",
"url": "https://yourapp.com/hooks/involok",
"secret": "whsec_abc123...",
"events": [
"invoice.paid"
]
}
}Available events: invoice.paid, invoice.sent, invoice.voided, file.unlocked, lokbox.purchased. Requests include X-Involok-Signature (HMAC-SHA256) and X-Involok-Event headers. The lokbox.purchased payload includes product_id and stripe_checkout_session_id.
/api/v1/webhooks/:idDelete a webhook
Parameters
idstringrequiredWebhook IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/webhooks/<id>' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": null
}Recurring Invoices
Automatically create and send invoices on a weekly, monthly, or quarterly schedule.
/api/v1/recurringList recurring schedules
Example request
curl 'https://involok.vercel.app
/api/v1/recurring' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": [
{
"id": "rec_01abc",
"recipient_name": "Acme Corp",
"recipient_email": "billing@acme.com",
"cadence": "monthly",
"is_active": true,
"next_run_at": "2026-04-01T09:00:00Z",
"invoices_created": 3
}
]
}/api/v1/recurringCreate a recurring schedule
Example request
curl -X POST 'https://involok.vercel.app
/api/v1/recurring' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"recipient_name": "Acme Corp",
"recipient_email": "billing@acme.com",
"line_items": [
{
"description": "Monthly retainer",
"quantity": 1,
"unit_amount": 500000
}
],
"cadence": "monthly",
"due_days": 14,
"send_immediately": true
}'Example response
{
"data": {
"id": "rec_01abc",
"cadence": "monthly",
"next_run_at": "2026-04-01T09:00:00Z"
}
}cadence: weekly | monthly | quarterly. Amounts in cents.
/api/v1/recurring/:idPause, resume, or reschedule. Set is_active: false to pause.
Parameters
idstringrequiredSchedule IDExample request
# Replace <id> with the actual resource ID
curl -X PATCH 'https://involok.vercel.app
/api/v1/recurring/<id>' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"is_active": false
}'Example response
{
"data": {
"id": "rec_01abc",
"is_active": false
}
}/api/v1/recurring/:idDelete a recurring schedule
Parameters
idstringrequiredSchedule IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/recurring/<id>' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"deleted": true
}
}Customers
Manage your customer address book. Customers are auto-saved when invoices are sent.
/api/v1/customersList customers
Parameters
qstringSearch by name or emailExample request
curl 'https://involok.vercel.app
/api/v1/customers' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": [
{
"id": "cus_01abc",
"name": "Jordan Lee",
"email": "jordan@example.com",
"created_at": "2026-03-01T00:00:00Z"
}
]
}/api/v1/customersUpserts a customer by email. If a customer with the same email exists, it is updated.
Example request
curl -X POST 'https://involok.vercel.app
/api/v1/customers' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"name": "Jordan Lee",
"email": "jordan@example.com"
}'Example response
{
"data": {
"id": "cus_01abc",
"name": "Jordan Lee",
"email": "jordan@example.com"
}
}/api/v1/customers/:idDelete a customer
Parameters
idstringrequiredCustomer IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/customers/<id>' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": null
}Lokbox
Manage your digital storefront products. Accepts both Bearer API keys (invoices:read / invoices:write scope) and session auth. Buyers access the public storefront at /lokbox/:slug.
/api/v1/lokbox/productsReturns all Lokbox products for the authenticated user, ordered by creation date descending.
Parameters
limitintegerMax results (default 50, max 100)offsetintegerPagination offsetExample request
curl 'https://involok.vercel.app
/api/v1/lokbox/products' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": [
{
"id": "prod_123",
"title": "Notion Template Pack",
"summary": "12 plug-and-play Notion templates",
"price_cents": 2500,
"sale_price_cents": 1500,
"content_type": "link",
"is_published": true,
"slug": "notion-template-pack",
"cover_image_url": "https://cdn.example.com/cover.jpg",
"show_sales_count": true,
"sales_count": 42,
"views": 310,
"details": [
{
"text": "Lifetime updates"
},
{
"text": "Commercial license"
}
],
"images": [
{
"url": "https://cdn.example.com/img1.jpg"
}
],
"created_at": "2026-03-20T12:00:00Z"
}
],
"pagination": {
"offset": 0,
"limit": 50,
"total": 1,
"has_more": false
}
}/api/v1/lokbox/productsCreates a new Lokbox storefront product. The storefront URL is /lokbox/:slug once published.
Parameters
titlestringrequiredProduct name (max 200 chars)content_typestringrequiredfile | text | linkprice_centsintegerPrice in cents. 0 = free (default 0)sale_price_centsintegerSale price in cents. Must be less than price_cents. Displays strikethrough pricing on storefrontsummarystringOne-liner shown on storefront card (max 300 chars)descriptionstringFull product descriptiondetailsarrayWhat's included bullets — array of { text: string } shown with checkmarks on the product modalimagesarrayGallery images — array of { url: string }. Max 8 images, each max 2 MB (JPEG/PNG/WebP)cover_image_urlstringPrimary thumbnail image URL. Must be one of the URLs in the images arrayshow_sales_countbooleanShow public sales count on storefront (default false)slugstringURL slug for the storefront page — lowercase, alphanumeric, hyphens onlyis_publishedbooleanMake product visible on storefront (default false)link_urlstringRequired when content_type is linktext_contentstringRequired when content_type is textExample request
curl -X POST 'https://involok.vercel.app
/api/v1/lokbox/products' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"title": "Notion Template Pack",
"summary": "12 plug-and-play Notion templates",
"description": "Everything you need to manage projects, habits, and finances in Notion.",
"price_cents": 2500,
"sale_price_cents": 1500,
"content_type": "link",
"link_url": "https://example.com/download",
"details": [
{
"text": "Lifetime updates"
},
{
"text": "Commercial license included"
}
],
"show_sales_count": true,
"is_published": false,
"slug": "notion-template-pack"
}'Example response
{
"data": {
"id": "prod_123",
"title": "Notion Template Pack",
"slug": "notion-template-pack",
"is_published": false,
"created_at": "2026-03-20T12:00:00Z"
}
}Prices are in cents. $15 = 1500. Upload images first via POST /lokbox/cover, then pass the returned URL in images[] and cover_image_url.
/api/v1/lokbox/products/:idReturns a single product with all fields including gallery images and details.
Parameters
idstringrequiredProduct IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/lokbox/products/<id>' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"id": "prod_123",
"title": "Notion Template Pack",
"summary": "12 plug-and-play Notion templates",
"price_cents": 2500,
"sale_price_cents": 1500,
"content_type": "link",
"is_published": true,
"slug": "notion-template-pack",
"cover_image_url": "https://cdn.example.com/cover.jpg",
"show_sales_count": true,
"sales_count": 42,
"views": 310,
"details": [
{
"text": "Lifetime updates"
}
],
"images": [
{
"url": "https://cdn.example.com/cover.jpg"
}
]
}
}/api/v1/lokbox/products/:idUpdates any fields on a product. Only include fields you want to change. Passing images replaces the entire gallery array.
Parameters
idstringrequiredProduct IDExample request
# Replace <id> with the actual resource ID
curl -X PATCH 'https://involok.vercel.app
/api/v1/lokbox/products/<id>' \
-H 'Authorization: Bearer inv_live_...' \
-H 'Content-Type: application/json' \
-d '{
"title": "Updated Title",
"sale_price_cents": 999,
"details": [
{
"text": "Lifetime updates"
},
{
"text": "New: Dark mode templates"
}
],
"is_published": true
}'Example response
{
"data": {
"id": "prod_123",
"title": "Updated Title",
"is_published": true
}
}Passing images: [] clears the gallery. Passing cover_image_url: null removes the thumbnail.
/api/v1/lokbox/products/:idPermanently deletes a product. The storefront URL immediately returns 404.
Parameters
idstringrequiredProduct IDExample request
# Replace <id> with the actual resource ID
curl 'https://involok.vercel.app
/api/v1/lokbox/products/<id>' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"deleted": true
}
}/api/v1/lokbox/coverUploads a single image to the product-images storage bucket. Use the returned URL in the images[] array or cover_image_url when creating or updating a product.
Parameters
fileFile (multipart)requiredJPEG, PNG, or WebP image. Max 2 MB.Example request
curl 'https://involok.vercel.app
/api/v1/lokbox/cover' \
-H 'Authorization: Bearer inv_live_...'Example response
{
"data": {
"cover_image_url": "https://cdn.supabase.co/storage/v1/object/public/product-images/user_id/img.jpg",
"path": "user_id/img.jpg"
}
}Request must be multipart/form-data. This endpoint requires session auth — use the dashboard to upload images when building via API.
Webhook verification
Every webhook delivery includes an X-Involok-Signature header. Verify it to confirm the request came from Involok.
import { createHmac } from "crypto";
function verifyWebhook(rawBody: string, signature: string, secret: string) {
const expected = createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return `sha256=${expected}` === signature;
}
// In your handler:
const sig = request.headers.get("x-involok-signature");
const isValid = verifyWebhook(await request.text(), sig, process.env.INVOLOK_WEBHOOK_SECRET);
if (!isValid) return new Response("Invalid signature", { status: 401 });