API Reference

v1.0.0

Automate 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 Unauthorized immediately

Available scopes

invoices:readRead invoices, line items, and Lokbox products
invoices:writeCreate, send, void invoices, and manage Lokbox products
files:readRead file metadata
files:writeUpload and delete files
full_accessBypass all individual scope checks

Errors

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 fields
401Unauthorized — API key missing, invalid, or revoked
403Forbidden — key lacks the required scope, or plan doesn't support API
404Not found — resource doesn't exist or belongs to another user
422Validation error — request body failed schema validation
429Rate limited — 100 requests per 60 seconds per key
500Internal server error

Invoices

Create and manage invoices, send them to recipients, and track payment status.

GET/api/v1/invoices
Bearer token·invoices:read

Returns a paginated list of invoices belonging to the authenticated user.

Parameters

statusstringFilter by status: draft | sent | paid | overdue | voided
limitnumberMax 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"
    }
  ]
}
POST/api/v1/invoices
Bearer token·invoices:write

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

GET/api/v1/invoices/:id
Bearer token·invoices:read

Returns a single invoice with its line items and attached file.

Parameters

idstringrequiredInvoice ID

Example 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
      }
    ]
  }
}
POST/api/v1/invoices/:id/send
Bearer token·invoices:write

Sends the invoice email to the recipient. Invoice must be in draft status.

Parameters

idstringrequiredInvoice ID

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

GET/api/v1/invoices/:id/pdf
Bearer token·invoices:read

Returns the invoice as a PDF file with your brand styling applied.

Parameters

idstringrequiredInvoice ID

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

PATCH/api/v1/invoices/:id
Bearer token·invoices:write

Updates fields on a draft invoice. Sending line_items replaces all existing items atomically.

Parameters

idstringrequiredInvoice ID

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

POST/api/v1/invoices/:id/mark-paid
Bearer token·invoices:write

Manually marks an invoice as paid without a Stripe payment. Useful for offline payments.

Parameters

idstringrequiredInvoice ID

Example 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"
  }
}
POST/api/v1/invoices/:id/remind
Bearer token·invoices:write

Resends the invoice email as a reminder. Only works for sent or overdue invoices.

Parameters

idstringrequiredInvoice ID

Example 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
  }
}
POST/api/v1/invoices/:id/void
Bearer token·invoices:write

Cancels an invoice. If already sent, the recipient receives a cancellation email.

Parameters

idstringrequiredInvoice ID

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

GET/api/v1/invoices/export
Bearer token·invoices:read

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

POST/api/v1/files
Bearer token·files:write

Uploads a file to storage. Optionally associate it with an invoice via invoice_id.

Parameters

fileFile (multipart)requiredThe file to upload
invoice_idstringAssociate with an existing invoice

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

GET/api/v1/files/:id
Bearer token·files:read

Returns metadata for a file. Does not return file contents.

Parameters

idstringrequiredFile ID

Example 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"
  }
}
DELETE/api/v1/files/:id
Bearer token·files:write

Permanently deletes a file from storage and the database.

Parameters

idstringrequiredFile ID

Example 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
  }
}
GET/api/v1/files/:id/download-url
No auth required

Returns a short-lived signed URL to download the file. File must be unlocked.

Parameters

idstringrequiredFile ID

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

GET/api/v1/webhooks
Bearer token

List 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"
    }
  ]
}
POST/api/v1/webhooks
Bearer token

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

DELETE/api/v1/webhooks/:id
Bearer token

Delete a webhook

Parameters

idstringrequiredWebhook ID

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

GET/api/v1/recurring
Bearer token·invoices:read

List 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
    }
  ]
}
POST/api/v1/recurring
Bearer token·invoices:write

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

PATCH/api/v1/recurring/:id
Bearer token·invoices:write

Pause, resume, or reschedule. Set is_active: false to pause.

Parameters

idstringrequiredSchedule ID

Example 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
  }
}
DELETE/api/v1/recurring/:id
Bearer token·invoices:write

Delete a recurring schedule

Parameters

idstringrequiredSchedule ID

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

GET/api/v1/customers
Bearer token·invoices:read

List customers

Parameters

qstringSearch by name or email

Example 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"
    }
  ]
}
POST/api/v1/customers
Bearer token·invoices:write

Upserts 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"
  }
}
DELETE/api/v1/customers/:id
Bearer token·invoices:write

Delete a customer

Parameters

idstringrequiredCustomer ID

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

GET/api/v1/lokbox/products
Bearer token·invoices:read

Returns all Lokbox products for the authenticated user, ordered by creation date descending.

Parameters

limitintegerMax results (default 50, max 100)
offsetintegerPagination offset

Example 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
  }
}
POST/api/v1/lokbox/products
Bearer token·invoices:write

Creates a new Lokbox storefront product. The storefront URL is /lokbox/:slug once published.

Parameters

titlestringrequiredProduct name (max 200 chars)
content_typestringrequiredfile | text | link
price_centsintegerPrice in cents. 0 = free (default 0)
sale_price_centsintegerSale price in cents. Must be less than price_cents. Displays strikethrough pricing on storefront
summarystringOne-liner shown on storefront card (max 300 chars)
descriptionstringFull product description
detailsarrayWhat's included bullets — array of { text: string } shown with checkmarks on the product modal
imagesarrayGallery 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 array
show_sales_countbooleanShow public sales count on storefront (default false)
slugstringURL slug for the storefront page — lowercase, alphanumeric, hyphens only
is_publishedbooleanMake product visible on storefront (default false)
link_urlstringRequired when content_type is link
text_contentstringRequired when content_type is text

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

GET/api/v1/lokbox/products/:id
Bearer token·invoices:read

Returns a single product with all fields including gallery images and details.

Parameters

idstringrequiredProduct ID

Example 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"
      }
    ]
  }
}
PATCH/api/v1/lokbox/products/:id
Bearer token·invoices:write

Updates any fields on a product. Only include fields you want to change. Passing images replaces the entire gallery array.

Parameters

idstringrequiredProduct ID

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

DELETE/api/v1/lokbox/products/:id
Bearer token·invoices:write

Permanently deletes a product. The storefront URL immediately returns 404.

Parameters

idstringrequiredProduct ID

Example 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
  }
}
POST/api/v1/lokbox/cover
Session auth only

Uploads 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 });
This reference is generated from the live API spec · v1.0.0