Overview
Quickstart
Authentication
Referrals
Rate Limits
Errors
Webhooks
Rule Conditions
Rule Rewards
Templates (SMS)
Messages (SMS)
API Console
Try the API live from your browser. Paste your API key, select an endpoint, and send a request.
Use a test key for exploration - test keys start with arv_test_.
Customers
Create, read, update, and delete loyalty customers. Each customer is linked to a branch and has a wallet balance.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
phone_number |
Char | Required | E.164 format |
display_name |
Char | Optional | None |
birth_date |
Date | Optional | None |
consent_status |
Boolean | Optional | None |
lifetime_spend |
Decimal | Read only | None |
last_visit_at |
DateTime | Read only | None |
is_active |
Boolean | Optional | None |
custom_data |
JSON | Optional | None |
branch_id |
UUID | Optional | None |
wallet_balance |
Computed | Read only | None |
tier_name |
Computed | Read only | None |
created_at |
DateTime | Read only | None |
updated_at |
DateTime | Read only | None |
Example Request
curl -X GET "https://arovadigital.com/api/v1/customers/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+233551234567",
"display_name": "Jane Doe",
"branch_id": "3a1b2c3d-...",
"birth_date": "1990-06-15"
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"phone_number": "+233551234567",
"display_name": "Jane Doe",
"branch_id": "3a1b2c3d-...",
"birth_date": "1990-06-15"
}
response = requests.get(
"https://arovadigital.com/api/v1/customers/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/customers/", {
method: "GET",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"phone_number": "+233551234567",
"display_name": "Jane Doe",
"branch_id": "3a1b2c3d-...",
"birth_date": "1990-06-15"
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "a1b2c3d4-...",
"phone_number": "+233551234567",
"display_name": "Jane Doe",
"wallet_balance": 250,
"tier_name": "Gold",
"branch_id": "3a1b2c3d-...",
"created_at": "2026-01-15T10:30:00Z"
}
Transactions
Record customer transactions and award loyalty points. Idempotent — use the idempotency_key to safely retry.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
tenant_customer_id |
UUID | Required | None |
branch_id |
UUID | Required | None |
amount |
Decimal | Required | None |
currency_code |
Char | Optional | None |
idempotency_key |
UUID | Optional | None |
metadata |
JSON | Optional | None |
points_earned |
Decimal | Read only | None |
txn_timestamp |
DateTime | Read only | None |
Example Request
curl -X GET "https://arovadigital.com/api/v1/transactions/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"tenant_customer_id": "a1b2c3d4-...",
"branch_id": "3a1b2c3d-...",
"amount": "150.00",
"currency_code": "GHS",
"idempotency_key": "e5f6g7h8-..."
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"tenant_customer_id": "a1b2c3d4-...",
"branch_id": "3a1b2c3d-...",
"amount": "150.00",
"currency_code": "GHS",
"idempotency_key": "e5f6g7h8-..."
}
response = requests.get(
"https://arovadigital.com/api/v1/transactions/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/transactions/", {
method: "GET",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"tenant_customer_id": "a1b2c3d4-...",
"branch_id": "3a1b2c3d-...",
"amount": "150.00",
"currency_code": "GHS",
"idempotency_key": "e5f6g7h8-..."
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "i9j0k1l2-...",
"points_earned": "75.0000",
"txn_timestamp": "2026-01-15T10:30:00Z"
}
Redemptions
Submit, approve, and reject customer redemption requests. Supports OTP and owner approval workflows.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
tenant_customer_id |
UUID | Required | None |
branch_id |
UUID | Required | None |
requested_points |
Decimal | Required | None |
requested_value |
Decimal | Optional | None |
reason |
Char | Optional | None |
status |
Choice | Read only | None |
requires_otp |
Boolean | Read only | None |
requires_owner_approval |
Boolean | Read only | None |
created_at |
DateTime | Read only | None |
resolved_at |
DateTime | Read only | None |
Example Request
curl -X GET "https://arovadigital.com/api/v1/redemptions/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"tenant_customer_id": "a1b2c3d4-...",
"branch_id": "3a1b2c3d-...",
"requested_points": 500,
"requested_value": "50.00",
"reason": "Birthday reward"
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"tenant_customer_id": "a1b2c3d4-...",
"branch_id": "3a1b2c3d-...",
"requested_points": 500,
"requested_value": "50.00",
"reason": "Birthday reward"
}
response = requests.get(
"https://arovadigital.com/api/v1/redemptions/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/redemptions/", {
method: "GET",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"tenant_customer_id": "a1b2c3d4-...",
"branch_id": "3a1b2c3d-...",
"requested_points": 500,
"requested_value": "50.00",
"reason": "Birthday reward"
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "m3n4o5p6-...",
"status": "pending",
"requires_otp": true,
"requires_owner_approval": false,
"created_at": "2026-01-15T10:30:00Z"
}
Campaigns
Manage loyalty campaigns. Each campaign can have multiple rules that define when and how points are earned.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
name |
Char | Required | None |
description |
Char | Optional | None |
starts_at |
DateTime | Optional | None |
ends_at |
DateTime | Optional | None |
priority |
Integer | Optional | None |
is_active |
Boolean | Optional | None |
rules |
ListSerializer | Read only | None |
created_at |
DateTime | Read only | None |
Example Request
curl -X GET "https://arovadigital.com/api/v1/campaigns/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Bonus",
"description": "Double points on all purchases",
"starts_at": "2026-06-01T00:00:00Z",
"ends_at": "2026-08-31T23:59:59Z",
"is_active": true
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"name": "Summer Bonus",
"description": "Double points on all purchases",
"starts_at": "2026-06-01T00:00:00Z",
"ends_at": "2026-08-31T23:59:59Z",
"is_active": true
}
response = requests.get(
"https://arovadigital.com/api/v1/campaigns/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/campaigns/", {
method: "GET",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Summer Bonus",
"description": "Double points on all purchases",
"starts_at": "2026-06-01T00:00:00Z",
"ends_at": "2026-08-31T23:59:59Z",
"is_active": true
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "q7r8s9t0-...",
"name": "Summer Bonus",
"is_active": true,
"rules": [],
"created_at": "2026-01-15T10:30:00Z"
}
Tiers
Configure loyalty tiers. Each tier defines the point multiplier and benefits for customers who reach it.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
name |
Char | Required | None |
min_points |
Decimal | Optional | None |
point_multiplier |
Decimal | Optional | None |
benefits_summary |
Char | Optional | None |
is_active |
Boolean | Optional | None |
created_at |
DateTime | Read only | None |
Example Request
curl -X GET "https://arovadigital.com/api/v1/tiers/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Gold",
"min_points": 1000,
"point_multiplier": "2.0",
"benefits_summary": "2x points, free delivery",
"is_active": true
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"name": "Gold",
"min_points": 1000,
"point_multiplier": "2.0",
"benefits_summary": "2x points, free delivery",
"is_active": true
}
response = requests.get(
"https://arovadigital.com/api/v1/tiers/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/tiers/", {
method: "GET",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Gold",
"min_points": 1000,
"point_multiplier": "2.0",
"benefits_summary": "2x points, free delivery",
"is_active": true
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "u1v2w3x4-...",
"name": "Gold",
"min_points": 1000,
"point_multiplier": "2.0",
"is_active": true,
"created_at": "2026-01-15T10:30:00Z"
}
Rules
Define loyalty rules for campaigns. Rules use a trigger/condition/reward pattern. This is covered in more detail below.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
campaign |
PrimaryKeyRelated | Optional | None |
name |
Char | Required | None |
trigger_type |
Char | Optional | None |
condition_json |
JSON | Optional | None |
reward_json |
JSON | Optional | None |
is_active |
Boolean | Optional | None |
created_at |
DateTime | Read only | None |
Example Request
curl -X GET "https://arovadigital.com/api/v1/rules/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"campaign": "q7r8s9t0-...",
"name": "Double points on weekends",
"trigger_type": "SPEND",
"condition_json": {
"days_of_week": [
5,
6
],
"min_spend": 20
},
"reward_json": {
"multiplier": 2.0
},
"is_active": true
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"campaign": "q7r8s9t0-...",
"name": "Double points on weekends",
"trigger_type": "SPEND",
"condition_json": {
"days_of_week": [
5,
6
],
"min_spend": 20
},
"reward_json": {
"multiplier": 2.0
},
"is_active": true
}
response = requests.get(
"https://arovadigital.com/api/v1/rules/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/rules/", {
method: "GET",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"campaign": "q7r8s9t0-...",
"name": "Double points on weekends",
"trigger_type": "SPEND",
"condition_json": {
"days_of_week": [
5,
6
],
"min_spend": 20
},
"reward_json": {
"multiplier": 2.0
},
"is_active": true
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "y5z6a7b8-...",
"campaign": "q7r8s9t0-...",
"name": "Double points on weekends",
"trigger_type": "purchase.completed",
"is_active": true,
"created_at": "2026-01-15T10:30:00Z"
}
Branches
List and retrieve business branches. Read-only — manage branches from the dashboard.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
name |
Char | Required | None |
location_label |
Char | Optional | None |
timezone |
Char | Optional | None |
currency_code |
Char | Optional | None |
is_active |
Boolean | Optional | None |
created_at |
DateTime | Read only | None |
Example Response
[
{
"id": "c9d0e1f2-...",
"name": "Accra Main",
"location_label": "Kotoka Airport, Accra",
"timezone": "Africa/Accra",
"currency_code": "GHS",
"is_active": true,
"created_at": "2026-01-15T10:30:00Z"
}
]
Send SMS
Send an SMS message to a phone number. You can provide the message body directly, or use a template. Idempotent — provide an idempotency key to safely retry without duplicates.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
event_type |
Char | Read only | None |
to_e164 |
Char | Read only | None |
status |
Choice | Read only | None |
provider |
Char | Read only | None |
sender_id |
Char | Read only | None |
estimated_segments |
Integer | Read only | None |
error_code |
Char | Read only | None |
error_message |
Char | Read only | None |
created_at |
DateTime | Read only | None |
sent_at |
DateTime | Read only | None |
delivered_at |
DateTime | Read only | None |
Example Request
curl -X POST "https://arovadigital.com/api/v1/messages/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"to_e164": "+233551234567",
"message": "Your order #1234 is ready for pickup.",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"to_e164": "+233551234567",
"message": "Your order #1234 is ready for pickup.",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}
response = requests.post(
"https://arovadigital.com/api/v1/messages/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/messages/", {
method: "POST",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"to_e164": "+233551234567",
"message": "Your order #1234 is ready for pickup.",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "d1e2f3a4-...",
"event_type": "API_MESSAGE",
"to_e164": "+233551234567",
"status": "SENT",
"provider": "hubtel",
"sender_id": "",
"estimated_segments": 1,
"error_code": null,
"error_message": null,
"created_at": "2026-01-15T10:30:00Z",
"sent_at": "2026-01-15T10:30:01Z",
"delivered_at": null
}
Send SMS with Template
Send an SMS using a template. The system renders the template body with the provided variable values before dispatching. You must provide template_id and template_variables instead of a raw message.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
event_type |
Char | Read only | None |
to_e164 |
Char | Read only | None |
status |
Choice | Read only | None |
provider |
Char | Read only | None |
sender_id |
Char | Read only | None |
estimated_segments |
Integer | Read only | None |
error_code |
Char | Read only | None |
error_message |
Char | Read only | None |
created_at |
DateTime | Read only | None |
sent_at |
DateTime | Read only | None |
delivered_at |
DateTime | Read only | None |
Example Request
curl -X POST "https://arovadigital.com/api/v1/messages/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"to_e164": "+233551234567",
"template_id": 1,
"template_variables": {
"customer_first_name": "Jane",
"merchant_name": "Arova"
},
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"to_e164": "+233551234567",
"template_id": 1,
"template_variables": {
"customer_first_name": "Jane",
"merchant_name": "Arova"
},
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}
response = requests.post(
"https://arovadigital.com/api/v1/messages/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/messages/", {
method: "POST",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"to_e164": "+233551234567",
"template_id": 1,
"template_variables": {
"customer_first_name": "Jane",
"merchant_name": "Arova"
},
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "d1e2f3a4-...",
"event_type": "API_MESSAGE",
"to_e164": "+233551234567",
"status": "SENT",
"provider": "hubtel",
"sender_id": "",
"estimated_segments": 1,
"created_at": "2026-01-15T10:30:00Z",
"sent_at": "2026-01-15T10:30:01Z"
}
List Messages
Retrieve all SMS messages sent through the API. Results are paginated and ordered by creation date (newest first).
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
event_type |
Char | Read only | None |
to_e164 |
Char | Read only | None |
status |
Choice | Read only | None |
provider |
Char | Read only | None |
sender_id |
Char | Read only | None |
estimated_segments |
Integer | Read only | None |
error_code |
Char | Read only | None |
error_message |
Char | Read only | None |
created_at |
DateTime | Read only | None |
sent_at |
DateTime | Read only | None |
delivered_at |
DateTime | Read only | None |
Example Response
[
{
"id": "d1e2f3a4-...",
"event_type": "API_MESSAGE",
"to_e164": "+233551234567",
"status": "DELIVERED",
"provider": "hubtel",
"sender_id": "LoveDesigns",
"estimated_segments": 1,
"created_at": "2026-01-15T10:30:00Z",
"sent_at": "2026-01-15T10:30:01Z",
"delivered_at": "2026-01-15T10:30:15Z"
}
]
Get Message
Retrieve a single SMS dispatch log by its ID. Use this to check the delivery status of a previously sent message.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
UUID | Read only | None |
event_type |
Char | Read only | None |
to_e164 |
Char | Read only | None |
status |
Choice | Read only | None |
provider |
Char | Read only | None |
sender_id |
Char | Read only | None |
estimated_segments |
Integer | Read only | None |
error_code |
Char | Read only | None |
error_message |
Char | Read only | None |
created_at |
DateTime | Read only | None |
sent_at |
DateTime | Read only | None |
delivered_at |
DateTime | Read only | None |
Example Response
{
"id": "d1e2f3a4-...",
"event_type": "API_MESSAGE",
"to_e164": "+233551234567",
"status": "DELIVERED",
"provider": "hubtel",
"sender_id": "LoveDesigns",
"estimated_segments": 1,
"error_code": null,
"error_message": null,
"created_at": "2026-01-15T10:30:00Z",
"sent_at": "2026-01-15T10:30:01Z",
"delivered_at": "2026-01-15T10:30:15Z"
}
Templates (SMS)
Create, read, update, and delete reusable SMS templates. Templates support variable substitution using <%variable_key%> syntax. Use them with the messages endpoint to send personalized SMS without building the message body every time.
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
Integer | Read only | None |
name |
Char | Required | None |
description |
Char | Optional | None |
body |
Char | Required | None |
required_variable_keys |
JSON | Optional | None |
is_active |
Boolean | Optional | None |
created_at |
DateTime | Read only | None |
updated_at |
DateTime | Read only | None |
Example Request
curl -X GET "https://arovadigital.com/api/v1/templates/" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"name": "Welcome SMS",
"description": "",
"body": "Hi <%customer_first_name%>, welcome to <%merchant_name%>!",
"required_variable_keys": [
"customer_first_name"
],
"is_active": true
}'
import requests
headers = {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
}
data = {
"name": "Welcome SMS",
"description": "",
"body": "Hi <%customer_first_name%>, welcome to <%merchant_name%>!",
"required_variable_keys": [
"customer_first_name"
],
"is_active": true
}
response = requests.get(
"https://arovadigital.com/api/v1/templates/",
headers=headers,
json=data,
)
print(response.json())
fetch("https://arovadigital.com/api/v1/templates/", {
method: "GET",
headers: {
"Authorization": "Bearer <YOUR_API_KEY>",
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "Welcome SMS",
"description": "",
"body": "Hi <%customer_first_name%>, welcome to <%merchant_name%>!",
"required_variable_keys": [
"customer_first_name"
],
"is_active": true
}),
})
.then(res => res.json())
.then(data => console.log(data));
Example Response
{
"id": "t1e2m3p4-...",
"name": "Welcome SMS",
"description": "",
"body": "Hi <%customer_first_name%>, welcome to <%merchant_name%>!",
"required_variable_keys": [
"customer_first_name"
],
"is_active": true,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-15T10:30:00Z"
}
Template Variables
List all available template variables for your merchant. Each variable includes its key, token format, group label, and kind. Use these variables in your template bodies with <%key%> syntax.
Example Response
[
{
"key": "customer_first_name",
"token": "<%customer_first_name%>",
"group": "Customer",
"kind": "standard"
},
{
"key": "points_balance",
"token": "<%points_balance%>",
"group": "Rewards",
"kind": "standard"
},
{
"key": "custom_favorite_store",
"token": "<%custom_favorite_store%>",
"group": "Custom Fields",
"kind": "custom_field"
}
]