Getting started
Quick start
This guide takes you from zero to a live payment link in about 10 minutes. You'll make your first authenticated request, create a contact, create a payment intent, and get a URL your customer can pay on. Every request below is copy-paste ready — swap in your API key and tenant ID and run it.
Before you begin
You need two things:
- An API key — create one in the Dashboard under Settings → API keys & webhooks. See Creating your API keys.
- Your tenant ID — find it alongside your API keys. See Multi-tenancy.
Every request is authenticated with two headers:
Authorization: ApiKey your-api-key-here
X-TENANT-ID: your-tenant-id-here
Base URL
All requests go to https://api.qualyhq.com/v1. The API only uses GET and POST, accepts and returns JSON, and works on one object per request. See Authorization.
Step 1: Make your first request
Before writing any integration code, confirm your credentials work. This GET returns the user tied to your API key:
curl https://api.qualyhq.com/v1/users/user \
-H 'Authorization: ApiKey your-api-key-here' \
-H 'X-TENANT-ID: your-tenant-id-here'
const res = await fetch('https://api.qualyhq.com/v1/users/user', {
headers: {
'Authorization': 'ApiKey your-api-key-here',
'X-TENANT-ID': 'your-tenant-id-here',
},
})
const { data: user } = await res.json()
console.log(user._id)
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'Authorization' => 'ApiKey your-api-key-here',
'X-TENANT-ID' => 'your-tenant-id-here',
])->get('https://api.qualyhq.com/v1/users/user');
$user = $response->json('data'); // unwrap the `data` envelope
echo $user['_id'];
A successful response looks like this:
{
"data": {
"_id": "6606ab7bfb9085579f1b5769",
"type": "api-key",
"email": "you@example.com"
}
}
Every response is wrapped in `data`
Qualy wraps every successful response in a { "data": ... } envelope. Your actual object is always under data — so it's response.data._id, not response._id. List endpoints add hasMore and count next to data for pagination. Keep this in mind for every example below.
If you get a 401, double-check the Authorization header format (ApiKey prefix, then the key). If you get a 400 Invalid Tenant ID, check your X-TENANT-ID.
Step 2: Set up your headers
From here we'll use JavaScript. Define your headers once and reuse them:
const API = 'https://api.qualyhq.com/v1';
const headers = {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
'X-TENANT-ID': 'your-tenant-id-here',
};
See Authorization for details on API keys, tenant IDs, and user types.
Step 3: Create a contact
A contact is the person who will pay. You create them once and reuse them across payments. A contact requires an email and a profile object:
curl -X POST https://api.qualyhq.com/v1/contacts/create \
-H 'Authorization: ApiKey your-api-key-here' \
-H 'X-TENANT-ID: your-tenant-id-here' \
-H 'Content-Type: application/json' \
-d '{
"email": "john.doe@example.com",
"profile": { "firstName": "John", "lastName": "Doe", "phone": "+61412345678" }
}'
const contactRes = await fetch(`${API}/contacts/create`, {
method: 'POST',
headers,
body: JSON.stringify({
email: 'john.doe@example.com',
profile: {
firstName: 'John',
lastName: 'Doe',
phone: '+61412345678',
},
}),
});
// Unwrap the `data` envelope
const { data: contact } = await contactRes.json();
console.log('Contact ID:', contact._id); // e.g. "6a4e0cec9a0ac53047a253f3"
use Illuminate\Support\Facades\Http;
$contact = Http::withHeaders([
'Authorization' => 'ApiKey your-api-key-here',
'X-TENANT-ID' => 'your-tenant-id-here',
])->post('https://api.qualyhq.com/v1/contacts/create', [
'email' => 'john.doe@example.com',
'profile' => [
'firstName' => 'John',
'lastName' => 'Doe',
'phone' => '+61412345678',
],
])->json('data');
echo $contact['_id']; // e.g. "6a4e0cec9a0ac53047a253f3"
You can skip IDs with smart references
Anywhere the API expects a reference (like the payment intent's contact field), you can pass a natural identifier such as the contact's email instead of its ObjectId. See Smart references.
Step 4: Create a payment intent
A payment intent defines what's owed: the contact, currency, and line items. Amounts are in minor units (cents), so 100000 is $1,000.00 AUD.
curl -X POST https://api.qualyhq.com/v1/payment-intents/create \
-H 'Authorization: ApiKey your-api-key-here' \
-H 'X-TENANT-ID: your-tenant-id-here' \
-H 'Content-Type: application/json' \
-d '{
"intentType": "ad-hoc",
"contact": "john.doe@example.com",
"currency": "AUD",
"dueAt": "2026-08-10T14:26:07.369Z",
"items": [{ "name": "Tuition fee", "category": "Course", "amount": 100000 }]
}'
const intentRes = await fetch(`${API}/payment-intents/create`, {
method: 'POST',
headers,
body: JSON.stringify({
intentType: 'ad-hoc',
contact: contact._id, // or 'john.doe@example.com' — see Smart references
currency: 'AUD',
dueAt: '2026-08-10T14:26:07.369Z',
items: [
{ name: 'Tuition fee', category: 'Course', amount: 100000 },
],
}),
});
const { data: paymentIntent } = await intentRes.json();
console.log('Payment intent ID:', paymentIntent._id);
console.log('Payment link:', paymentIntent.links.short ?? paymentIntent.links.long);
use Illuminate\Support\Facades\Http;
$paymentIntent = Http::withHeaders([
'Authorization' => 'ApiKey your-api-key-here',
'X-TENANT-ID' => 'your-tenant-id-here',
])->post('https://api.qualyhq.com/v1/payment-intents/create', [
'intentType' => 'ad-hoc',
'contact' => $contact['_id'], // or 'john.doe@example.com' — see Smart references
'currency' => 'AUD',
'dueAt' => '2026-08-10T14:26:07.369Z',
'items' => [
['name' => 'Tuition fee', 'category' => 'Course', 'amount' => 100000],
],
])->json('data');
echo $paymentIntent['links']['short'] ?? $paymentIntent['links']['long'];
Which link do I send?
The response's links.long is always present and is the canonical payment URL. links.short is a shortened, share-friendly version that's added when available — prefer it for emails, SMS, and WhatsApp, and fall back to links.long.
Use intentType: 'ad-hoc' for one-off charges. For payment plans and installments, see Creating payment intents and Creating orders.
Step 5: Collect the payment
Send the payment link from Step 4 to your customer. When they open it, Qualy's hosted payment page handles method selection, payer verification, and processing:
const paymentUrl = paymentIntent.links.short ?? paymentIntent.links.long;
// Redirect the customer here, or send it by email/SMS/WhatsApp.
This hosted flow is the fastest way to start and is required for card payments. If you need to render non-card methods (PIX, Boleto, PayID, bank transfers) inside your own UI, see Collecting payments.
Step 6: Know when you get paid
Poll the payment intent to check its status:
const statusRes = await fetch(`${API}/payment-intents/${paymentIntent._id}`, {
method: 'GET',
headers,
});
const { data: latest } = await statusRes.json();
console.log('Status:', latest.status); // "due" → "paid-full" (or "paid-partial")
For production, don't poll — set up webhooks so Qualy notifies your server the moment a payment succeeds or fails.
Complete example
The whole flow in one script:
const API = 'https://api.qualyhq.com/v1';
const headers = {
'Content-Type': 'application/json',
'Authorization': 'ApiKey your-api-key-here',
'X-TENANT-ID': 'your-tenant-id-here',
};
// 1. Create a contact
const { data: contact } = await fetch(`${API}/contacts/create`, {
method: 'POST', headers,
body: JSON.stringify({
email: 'john.doe@example.com',
profile: { firstName: 'John', lastName: 'Doe', phone: '+61412345678' },
}),
}).then((r) => r.json());
// 2. Create a payment intent
const { data: paymentIntent } = await fetch(`${API}/payment-intents/create`, {
method: 'POST', headers,
body: JSON.stringify({
intentType: 'ad-hoc',
contact: contact._id,
currency: 'AUD',
dueAt: '2026-08-10T14:26:07.369Z',
items: [{ name: 'Tuition fee', category: 'Course', amount: 100000 }],
}),
}).then((r) => r.json());
// 3. Send this link to your customer
console.log('Pay here:', paymentIntent.links.short ?? paymentIntent.links.long);
// 4. Check the status later
const { data: latest } = await fetch(
`${API}/payment-intents/${paymentIntent._id}`,
{ headers },
).then((r) => r.json());
console.log('Status:', latest.status);
use Illuminate\Support\Facades\Http;
$api = Http::baseUrl('https://api.qualyhq.com/v1')->withHeaders([
'Authorization' => 'ApiKey your-api-key-here',
'X-TENANT-ID' => 'your-tenant-id-here',
]);
// 1. Create a contact
$contact = $api->post('/contacts/create', [
'email' => 'john.doe@example.com',
'profile' => ['firstName' => 'John', 'lastName' => 'Doe', 'phone' => '+61412345678'],
])->json('data');
// 2. Create a payment intent
$paymentIntent = $api->post('/payment-intents/create', [
'intentType' => 'ad-hoc',
'contact' => $contact['_id'],
'currency' => 'AUD',
'dueAt' => '2026-08-10T14:26:07.369Z',
'items' => [['name' => 'Tuition fee', 'category' => 'Course', 'amount' => 100000]],
])->json('data');
// 3. Send this link to your customer
echo $paymentIntent['links']['short'] ?? $paymentIntent['links']['long'];
// 4. Check the status later
$latest = $api->get("/payment-intents/{$paymentIntent['_id']}")->json('data');
echo $latest['status'];
Next steps
- Get notified automatically — set up webhooks instead of polling.
- Understand the envelope and filtering — see Querying data.
- Offer more payment methods — PIX, Boleto, PayID and more in Collecting payments.
- Bill over time — orders and subscriptions for plans and recurring payments.
- Make requests safely retryable — Idempotency.
- Handle failures well — Errors.
- Explore everything — the full API reference.