Enter at least two characters to search. Searching documentation… No matching sections. Try an endpoint, field or event name.
Invoices
View as MarkdownAn invoice bills one named customer for a fixed amount, and suits a bill to pay later or a customer who should choose the coin. Unlike a charge it needs their name and email but no coin, and it has no timer: it stays open until it is paid or you void it, and the exchange rate is only locked when the customer opens its page and chooses a coin. Every attempt to pay is a charge with invoice set, so the usual charge events and webhooks apply; the invoice becomes paid when one of its charges does, even after it was voided.
On this page
The invoice object
| Field | Type | Description |
|---|---|---|
id
string
|
string | Public code, the last path segment of hosted_url. |
object
"invoice"
|
"invoice" | The kind of object. |
number
string
|
string | Sequential per account and mode, e.g. INV-0042: test invoices are numbered on their own. You can set your own prefix for new invoices in your organization's settings. |
status
"open" | "paid" | "void"
|
"open" | "paid" | "void" | Open until one of its charges is paid or you void it. A void invoice still becomes paid if you later accept a late payment on one of its charges. |
overdue
boolean
|
boolean | Open and past the end of due_at (UTC). Overdue invoices can still be paid. |
customer_name, customer_email
string
|
string | Who is billed, up to 255 characters each. The invoice and the receipt go to this email. |
memo
string|null
|
string|null | Shown on the invoice page and in the email, 500 characters max. |
amount, currency
string
|
string | What is owed, in your currency: 0.01 or more, with two decimals. |
due_at
string|null
|
string|null | Date as YYYY-MM-DD, today or later when the invoice is created. |
items
array
|
array | The lines of an itemised invoice, up to 50: description (up to 255 characters), quantity (more than 0, up to 999,999, up to three decimals), unit_amount (0 or more, two decimals) and total (quantity × unit_amount, rounded to the cent), all as strings, such as "1.5". Empty when the invoice carries a single amount. |
hosted_url
string
|
string | The page the customer pays on. |
document_url
string
|
string | A printable version of the invoice, which the customer can save as a PDF. |
charges
array
|
array | Payment attempts, newest first: id, status, asset. |
sent_at, viewed_at, paid_at, voided_at, created_at
string|null
|
string|null | ISO 8601 timestamps in UTC. sent_at is the first email that was not a reminder; viewed_at the first time someone outside your account opened hosted_url. |
Endpoints
-
GET
/v1/invoicesList invoices, newest first. Filter with ?status=, ?search= (the number, customer name or customer email) and ?overdue=true. -
POST
/v1/invoicesCreate an invoice. Body: customer_name, customer_email, currency, memo, due_at, send_email and either amount or items (description, quantity, unit_amount each; the amount is then their sum, which must come to at least 0.01, and an amount sent alongside is ignored). In test mode, send_email to anyone who is not a member of your account answers 422 under errors.customer_email and creates nothing; past the hourly email allowance (see send), send_email answers 429 with Retry-After and creates nothing. -
GET
/v1/invoices/{id}Retrieve an invoice with its payment attempts. -
POST
/v1/invoices/{id}/sendEmail the invoice to the customer. The first email sets sent_at; any later one goes out as a reminder. 409 unless it is open; 429 with Retry-After within 10 minutes of the last email, or past 100 emails an hour for the account (invoices, payment requests and refund notices together, shared with the other accounts its owner owns). In test mode, invoices are only emailed to members of your account: anyone else answers 422 under errors.customer_email. -
POST
/v1/invoices/{id}/voidWithdraw an open invoice and cancel its pending charges, each with a charge.canceled event. 409 when it is not open, or one of its charges is underpaid or has a payment confirming.
Example: create an invoice
Copy failed. Select the code and copy it manually.
curl -X POST 'https://api.velirapay.com/v1/invoices' \
--fail-with-body --silent --show-error --max-time 30 \
-H "Authorization: Bearer ${VELIRAPAY_API_KEY:?Set VELIRAPAY_API_KEY}" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"currency": "EUR",
"items": [
{
"description": "Consulting",
"quantity": "2",
"unit_amount": "75.00"
}
],
"due_at": "2026-12-31",
"send_email": true
}'
<?php
use VeliraPay\VeliraPayClient;
require __DIR__.'/vendor/autoload.php';
$apiKey = getenv('VELIRAPAY_API_KEY') ?: throw new RuntimeException('Set VELIRAPAY_API_KEY.');
$velirapay = new VeliraPayClient($apiKey, baseUrl: 'https://api.velirapay.com');
$invoice = $velirapay->invoices->create([
'customer_name' => 'Jane Doe',
'customer_email' => 'jane@example.com',
'currency' => 'EUR',
'items' => [
[
'description' => 'Consulting',
'quantity' => '2',
'unit_amount' => '75.00',
],
],
'due_at' => '2026-12-31',
'send_email' => true,
]);
echo json_encode($invoice, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), PHP_EOL;
use VeliraPay\Laravel\Facades\VeliraPay;
$invoice = VeliraPay::invoices()->create([
'customer_name' => 'Jane Doe',
'customer_email' => 'jane@example.com',
'currency' => 'EUR',
'items' => [
[
'description' => 'Consulting',
'quantity' => '2',
'unit_amount' => '75.00',
],
],
'due_at' => '2026-12-31',
'send_email' => true,
]);
const secretKey = process.env.VELIRAPAY_API_KEY;
if (!secretKey) throw new Error('Set VELIRAPAY_API_KEY.');
const response = await fetch('https://api.velirapay.com/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${secretKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer_name: 'Jane Doe',
customer_email: 'jane@example.com',
currency: 'EUR',
items: [
{
description: 'Consulting',
quantity: '2',
unit_amount: '75.00',
},
],
due_at: '2026-12-31',
send_email: true,
}),
redirect: 'error',
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) {
throw new Error(`VeliraPay ${response.status}: ${await response.text()}`);
}
const invoice = response.status === 204 ? null : (await response.json()).data;
console.log(JSON.stringify(invoice, null, 2));
import json
import os
import requests
api_key = os.environ["VELIRAPAY_API_KEY"]
response = requests.post(
"https://api.velirapay.com/v1/invoices",
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
},
json={
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"currency": "EUR",
"items": [
{
"description": "Consulting",
"quantity": "2",
"unit_amount": "75.00",
},
],
"due_at": "2026-12-31",
"send_email": True,
},
timeout=(5, 30),
allow_redirects=False,
)
if not 200 <= response.status_code < 300:
raise RuntimeError(f"VeliraPay {response.status_code}: {response.text}")
invoice = None if response.status_code == 204 else response.json()["data"]
print(json.dumps(invoice, indent=2))
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var secretKey = Environment.GetEnvironmentVariable("VELIRAPAY_API_KEY")
?? throw new InvalidOperationException("Set VELIRAPAY_API_KEY.");
using var client = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false })
{
Timeout = TimeSpan.FromSeconds(30),
};
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.velirapay.com/v1/invoices");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = new StringContent("""
{
"customer_name": "Jane Doe",
"customer_email": "jane@example.com",
"currency": "EUR",
"items": [
{
"description": "Consulting",
"quantity": "2",
"unit_amount": "75.00"
}
],
"due_at": "2026-12-31",
"send_email": true
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"VeliraPay {(int)response.StatusCode}: {responseBody}");
}
JsonElement? invoice = null;
if (response.StatusCode != HttpStatusCode.NoContent)
{
using var document = JsonDocument.Parse(responseBody);
invoice = document.RootElement.GetProperty("data").Clone();
}
Console.WriteLine(invoice?.GetRawText() ?? "null");
Copy failed. Select the code and copy it manually.
{
"data": {
"id": "q7m2xk9wd4ra",
"object": "invoice",
"number": "INV-0042",
"status": "open",
"overdue": false,
"amount": "150.00",
"currency": "EUR",
"items": [
{"description": "Consulting", "quantity": "2", "unit_amount": "75.00", "total": "150.00"}
],
"due_at": "2026-12-31",
"hosted_url": "https://velirapay.com/i/q7m2xk9wd4ra",
…
}
}
Reminders
A reminder raises invoice.sent with details.reminder set to true and leaves sent_at as it is. Sending an invoice that was already emailed sends a reminder, and so does the platform on its own schedule.
Turn on invoice reminders under Payment settings and open live invoices with a due date are reminded automatically: the day before it falls due, then 3 and 10 days after, at most once a day, from 09:00 UTC. A step that passed while reminders were off is skipped rather than sent late, and test invoices are never reminded.