Agilis API Documentation
The Agilis REST API lets advertisers manage offers programmatically. All requests are made over
HTTPS to https://ads.agilis.dating and authenticated with your API key, supplied in
the x-api-key request header. Responses are returned as JSON.
Developer Guide
This guide walks through the complete offer creation flow, from fetching categories to managing a live offer. The Agilis Advertising API lets developers create and manage advertiser offers programmatically.
- Base URL:
https://ads.agilis.dating - Authentication: All requests require a valid API key in the
x-api-keyrequest header. - Content-Type:
application/jsonfor JSON requests;multipart/form-datafor offer creation.
Overview
The offer creation flow involves up to five steps, depending on the payment method chosen:
1. Fetch Categories GET /categories
2. Estimate Price POST /offers/price-estimate (recommended before creating)
3. Create Offer POST /offers
├─ Payment method E ──► Stripe checkout link returned
│ User completes payment externally
│ Webhook activates offer → Active or Scheduled
└─ Payment method B ──► Ad Units deducted from balance immediately
Offer becomes Active or Scheduled at once
Once the offer exists you can view it, pause/resume it, retrieve analytics, or delete it.
1 Fetch Categories
Before creating an offer you need a valid category_id. Retrieve the categories assigned to your organisation.
Endpoint: GET /categories
curl -X GET "https://ads.agilis.dating/categories" \
-H "x-api-key: YOUR_API_KEY_HERE"
Successful response — HTTP 200
{
"categories": [
{ "category_id": 1, "category_name": "Retail" },
{ "category_id": 2, "category_name": "Food & Beverage" },
{ "category_id": 3, "category_name": "Health & Fitness" }
]
}
Use the category_id value in Step 3.
Error responses
| HTTP | Body | Meaning |
|---|---|---|
| 401 | {"message": "Unauthorised"} | API key is missing, invalid, or disabled; or the associated user account is disabled |
| 500 | {"message": "Internal server error"} | Unexpected server error |
2 Estimate Price Recommended
Calculate how many Ad Units the offer will cost before committing. This is especially important if you intend to pay from balance (payment method B) so you can confirm there are sufficient credits.
Endpoint: POST /offers/price-estimate
Price formula: Duration in hours × number of Agilis users within the target radius. The result is a float rounded to 2 decimal places.
curl -X POST "https://ads.agilis.dating/offers/price-estimate" \
-H "x-api-key: YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"start_datetime": "2026-08-01T09:00:00Z",
"end_datetime": "2026-08-31T23:59:00Z",
"location": [51.5074, -0.1278],
"radius": 10
}'
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
start_datetime | string | Yes | ISO 8601, UTC only (e.g. 2026-08-01T09:00:00Z) |
end_datetime | string | Yes | ISO 8601, UTC only |
location | array | Yes | [latitude, longitude] |
radius | integer | Yes | Miles — minimum 5, maximum 30 |
Successful response — HTTP 200, S1
{
"return_code": "S1",
"price_estimate": 45.50
}
Error responses
| HTTP | Body | Meaning |
|---|---|---|
| 400 | {"return_code": "E1", ...} | One or more inputs are missing or invalid |
| 400 | {"return_code": "E2", ...} | start_datetime or end_datetime is not in valid ISO 8601 format |
| 401 | {"message": "Unauthorised"} | Authentication failure |
3 Create the Offer
Submit the offer as a multipart/form-data request. The image must be sent as a binary file stream. The API runs automated content moderation on the image before the offer is created. If moderation fails, the offer is not created and no charge is made.
Endpoint: POST /offers
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
title | string | Yes | |
description | string | Yes | |
terms_and_conditions | string | Yes | Maximum 500 characters |
category_id | integer | Yes | From Step 1 |
url | string | Yes | Advertiser's offer URL |
image | file (binary) | Yes | JPG, PNG or GIF; maximum 10 MB; sent as binary stream |
currency | string | Yes | GBP, USD, CAD or AUD (case-insensitive) |
location[] | float | Yes | Two values: latitude then longitude |
radius | integer | Yes | Miles — minimum 5 |
start_datetime | string | Yes | ISO 8601, UTC (e.g. 2026-08-01T09:00:00Z) |
expiry_datetime | string | Yes | ISO 8601, UTC; minimum 60 seconds after start_datetime |
payment_method | string | Yes | E = Stripe link by email; B = deduct from organisation balance |
environment | string | Yes | LIVE or TEST (case-insensitive) |
POST /offers request must include an Idempotency-Key header — a unique value you generate (e.g. a UUID v4). Replaying the same key safely retries the request without creating a duplicate offer or a second Stripe payment; the original response (including any payment_url) is returned verbatim. A request with no Idempotency-Key header is rejected with HTTP 400 / E1.Path A — Payment by Stripe link (payment_method=E)
The offer is created in Awaiting Payment status. A Stripe Checkout link is generated and returned. The organisation admin receives the link and completes payment externally. When Stripe confirms payment via webhook, the offer is automatically activated.
curl -X POST "https://ads.agilis.dating/offers" \
-H "x-api-key: YOUR_API_KEY_HERE" \
-H "Idempotency-Key: 3f1a9c7e-1b2d-4f9a-8c0e-2a6b5d4e7f10" \
-F "title=Summer Sale — 20% Off" \
-F "description=Discount valid through August on all summer items." \
-F "terms_and_conditions=Valid for online purchases only. One per customer." \
-F "category_id=1" \
-F "url=https://www.example.com/summer-sale" \
-F "currency=GBP" \
-F "location[]=51.5074" \
-F "location[]=-0.1278" \
-F "radius=10" \
-F "start_datetime=2026-08-01T09:00:00Z" \
-F "expiry_datetime=2026-08-31T23:59:00Z" \
-F "payment_method=E" \
-F "environment=LIVE" \
-F "image=@/path/to/offer-image.jpg"
Response immediately after creation — HTTP 200, S1
The offer is created but not yet active. Store the offer_id and present the payment_url to the user.
{
"return_code": "S1",
"offer_id": 45,
"status": "Awaiting Payment",
"payment_url": "https://checkout.stripe.com/c/pay/cs_live_..."
}
Direct your user to this Stripe Checkout page. The quantity on the Stripe invoice equals the number of Ad Units calculated in Step 2. Your user enters their card details and completes payment on Stripe's hosted page.
After payment, Stripe sends a checkout.session.completed webhook to Agilis, which activates the offer automatically. The status becomes Active if start_datetime is now or in the past, or Scheduled if it is in the future.
Path B — Payment from organisation balance (payment_method=B)
Before calling this path, confirm that the organisation's Ad Unit balance is at least equal to the price estimate returned in Step 2. If the balance is insufficient, the API returns E3 and no offer is created. Payment is deducted immediately when the offer is created; no Stripe link is involved.
curl -X POST "https://ads.agilis.dating/offers" \
-H "x-api-key: YOUR_API_KEY_HERE" \
-H "Idempotency-Key: 3f1a9c7e-1b2d-4f9a-8c0e-2a6b5d4e7f10" \
-F "title=Summer Sale — 20% Off" \
-F "description=Discount valid through August on all summer items." \
-F "terms_and_conditions=Valid for online purchases only. One per customer." \
-F "category_id=1" \
-F "url=https://www.example.com/summer-sale" \
-F "currency=GBP" \
-F "location[]=51.5074" \
-F "location[]=-0.1278" \
-F "radius=10" \
-F "start_datetime=2026-08-01T09:00:00Z" \
-F "expiry_datetime=2026-08-31T23:59:00Z" \
-F "payment_method=B" \
-F "environment=LIVE" \
-F "image=@/path/to/offer-image.jpg"
Response when start date is in the future — HTTP 200, S1
{
"return_code": "S1",
"offer_id": 46,
"status": "Scheduled",
"payment_url": null
}
Response when start date has already passed — HTTP 200, S1
{
"return_code": "S1",
"offer_id": 47,
"status": "Active",
"payment_url": null
}
Ad Units are deducted from the organisation balance and Org Admins are notified by email in both cases.
Create Offer error responses (both payment paths)
| HTTP | Body | Meaning |
|---|---|---|
| 400 | E1 | One or more inputs are missing or invalid (unsupported currency, invalid datetime, duration under 60 seconds, radius under 5 miles, oversized image, or wrong MIME type) |
| 200 | E2 | Image failed automated content moderation — offer not created; choose a different image |
| 200 | E3 | Organisation's Ad Unit balance is insufficient — top up and retry (payment_method=B only) |
| 400 | E1 | Idempotency-Key request header was missing (message: "Idempotency-Key header is required.") |
| 409 | — | Idempotency-Key reused while the first request is still being processed — retry shortly |
| 422 | — | Idempotency-Key reused with a different payload |
| 401 | Unauthorised | Authentication failure |
| 500 | Internal server error | Unexpected server error |
4 Verify the Offer Was Created
Confirm creation and check the current status by calling View Offers.
curl -X GET "https://ads.agilis.dating/offers?environment=live" \
-H "x-api-key: YOUR_API_KEY_HERE"
offer_status values
| Value | Meaning |
|---|---|
Awaiting Payment | Created via method E; payment not yet received |
Active | Live and visible to Agilis users |
Scheduled | Paid for but start date is in the future |
Paused | Temporarily paused by the advertiser |
Ended | Expiry datetime has passed |
The environment query parameter is mandatory; use test or live (case-insensitive).
GET /offers returns a paginated response shaped as { "data": […], "pagination": { "limit", "count", "has_more", "next_cursor" } }. Page with limit (default 20, max 100) and cursor, and optionally filter with offer_status, created_after and created_before. Each offer also carries a status field (enabled or disabled); deleted offers are never returned.Managing an Offer After Creation
Use the pause, resume, analytics, and delete endpoints to manage an offer once it exists. Valid analytics interval values are 24h, 7d, 30d, 60d, 90d, and all (mandatory). Deleting an offer in Awaiting Payment status automatically disables its Stripe payment link first. Full request and response details for each are documented in the Pause and Resume, Analytics, and Delete Offer sections below.
Authentication Notes
- API keys are created in the Agilis Ad Portal under Manage API Keys (Developer role).
- Keys can be scoped to TEST or LIVE environments using the portal's test/live mode toggle.
- A disabled key or disabled user account returns
HTTP 401 {"message": "Unauthorised"}on any endpoint. - Keys have an optional expiry date; expired keys also return 401.
Environment Testing
Set environment: TEST in the Create Offer request to create offers in a sandbox. Test offers are only returned when calling GET /offers?environment=test, do not appear to real Agilis users in the app, and follow the same creation, payment, and management flow as live offers. Switch between environments using the test/live mode toggle in the Ad Portal.
Category List
The Category List API returns all active categories available to the organisation that owns the API key. It is typically called before creating an offer, because offer creation requires a valid category ID returned by this endpoint.
Header: x-api-key: your_api_key_here
Method: GET
category_id integer
Unique ID of the category.
Example: 1
category_name string
Name of the category.
Example: "Retail"
200 — Categories retrieved successfully.
401 — Invalid API key or disabled account.
500 — Internal server error.
import requests
url = "https://ads.agilis.dating/categories"
headers = {"x-api-key": "your_api_key_here"}
response = requests.get(url, headers=headers)
print(response.json()){
"categories": [
{ "category_id": 1, "category_name": "Retail" },
{ "category_id": 2, "category_name": "Food & Beverage" }
]
}
Create Offer
Creates a new offer for the authenticated advertiser. The request is sent as
multipart/form-data with the offer image supplied as a binary file stream.
The image is checked by automated content moderation before the offer is created.
Header: x-api-key: your_api_key_here
Header: Idempotency-Key: <unique-uuid> required
Content-Type: multipart/form-data
category_id integer, required
A valid category ID from the Category List API.
Example: 1
title string, required
Example: "20% off summer sale"
description string, required
Example: "Discount valid through July"
terms_and_conditions string, required
Maximum 500 characters.
Example: "Valid online only."
url string, required
Example: "https://advertiseroffer.com"
image binary, required
JPG, PNG or GIF, maximum 10MB, sent as a binary file stream.
Recommended size: 1080x1080px
currency string, required
One of GBP, USD, CAD, AUD (case-insensitive).
location[] array, required
Latitude and longitude as two values.
Example: [51.5074, -0.1278]
radius integer, required
Radius in miles, minimum 5.
Example: 10
start_datetime string, required
ISO 8601, UTC.
Example: "2026-07-10T08:00:00Z"
expiry_datetime string, required
ISO 8601, UTC.
Example: "2026-07-12T20:30:00Z"
payment_method string, required
E = Stripe payment link by email; B = deduct from organisation balance.
environment string, required
One of TEST or LIVE (case-insensitive).
S1 — Offer created (payment link issued, or Live/Scheduled if paid from balance).
E1 — One or more inputs are missing or invalid (including unsupported currency).
E2 — The uploaded image was rejected by content moderation.
E3 — Insufficient Ad Unit balance.
409 — Idempotency-Key reused while the first request is still processing.
422 — Idempotency-Key reused with a different payload.
import requests
url = "https://ads.agilis.dating/offers"
headers = {
"x-api-key": "your_api_key_here",
"Idempotency-Key": "3f1a9c7e-1b2d-4f9a-8c0e-2a6b5d4e7f10"
}
files = {
"image": ("offer.jpg", open("/path/to/offer.jpg", "rb"), "image/jpeg")
}
data = {
"category_id": 1,
"title": "20% off summer sale",
"description": "Discount valid through July",
"terms_and_conditions": "Valid online only.",
"url": "https://advertiseroffer.com",
"currency": "GBP",
"location[]": [51.5074, -0.1278],
"radius": 10,
"start_datetime": "2026-07-10T08:00:00Z",
"expiry_datetime": "2026-07-12T20:30:00Z",
"payment_method": "E",
"environment": "LIVE"
}
response = requests.post(url, headers=headers, files=files, data=data)
print(response.json()){
"return_code": "S1",
"offer_id": 45,
"status": "Awaiting Payment",
"payment_url": "https://checkout.stripe.com/c/pay/cs_test_..."
}
View Offers
Returns a paginated list of offers created by the authenticated advertiser for the requested
environment, newest first. Offers whose image was rejected by content moderation are never created,
so they are not returned, and deleted offers are excluded. Page through results with
limit and cursor.
Header: x-api-key: your_api_key_here
Method: GET
environment string, required
One of test or live
(case-insensitive).
Example: /offers?environment=test
limit integer, optional
Page size. Default 20, maximum 100.
cursor string, optional
Opaque token from the previous page's next_cursor. Omit for the first page.
offer_status string, optional
Lifecycle filter: awaiting_payment, active, paused or ended (case-insensitive).
created_after string, optional
ISO 8601, UTC. Offers created at or after this time.
created_before string, optional
ISO 8601, UTC. Offers created at or before this time.
Each offer object contains: offer_id, title, category,
description, terms_and_conditions, url,
image (signed CloudFront URL), latitude, longitude,
radius, gender (array such as ["Male","Female"], or
null), start_datetime, expiry_datetime,
creation_datetime, offer_status
(Active, Paused, Ended or Awaiting Payment) and status
(enabled or disabled — whether an Admin has disabled the offer).
200 — Page returned (empty
data array if none match).400 — Invalid query parameter (
limit, cursor, offer_status or date).401 — Invalid API key or disabled account.
500 — Internal server error.
import requests
url = "https://ads.agilis.dating/offers"
headers = {"x-api-key": "your_api_key_here"}
params = {"environment": "test", "limit": 20}
response = requests.get(url, headers=headers, params=params)
print(response.json()){
"data": [
{
"offer_id": 45,
"title": "20% off summer sale",
"category": "Retail",
"description": "Discount valid through July",
"terms_and_conditions": "Valid online only.",
"url": "https://advertiseroffer.com",
"image": "https://ads.agilis.dating/92/offers/45/offer_orig.png",
"latitude": 51.5074,
"longitude": -0.1278,
"radius": 10,
"gender": ["Male", "Female"],
"start_datetime": "2026-07-10T08:00:00Z",
"expiry_datetime": "2026-07-12T20:30:00Z",
"creation_datetime": "2026-06-01T10:00:00Z",
"offer_status": "Active",
"status": "enabled"
}
],
"pagination": {
"limit": 20,
"count": 1,
"has_more": false,
"next_cursor": null
}
}
Pause and Resume Offer
Pauses a running offer or resumes a paused offer. These are two separate endpoints. The offer must belong to the account that owns the API key.
Header: x-api-key: your_api_key_here
Method: POST
offer_id integer, required
Path parameter.
Example: 45
S1 — Offer paused successfully.
S2 — Offer resumed successfully.
E2 — Offer has already ended.
E3 — Offer is already paused (pause request).
E4 — Offer is already running (resume request).
E5 — Offer is awaiting payment.
import requests
headers = {"x-api-key": "your_api_key_here"}
# Pause an offer
pause = requests.post(
"https://ads.agilis.dating/offers/45/pause", headers=headers)
print(pause.json())
# Resume an offer
resume = requests.post(
"https://ads.agilis.dating/offers/45/resume", headers=headers)
print(resume.json()){
"return_code": "S1",
"message": "Offer has been paused."
}
Delete Offer
Deletes an offer belonging to the authenticated account. If the offer is awaiting payment, its Stripe payment link is disabled before deletion.
Header: x-api-key: your_api_key_here
Method: DELETE
offer_id integer, required
Path parameter.
Example: 45
S1 — Offer deleted successfully.
E1 — Invalid input, or the offer does not belong to this account.
import requests
url = "https://ads.agilis.dating/offers/45"
headers = {"x-api-key": "your_api_key_here"}
response = requests.delete(url, headers=headers)
print(response.json()){
"return_code": "S1",
"message": "Offer has been deleted."
}
View Offer Analytics
Returns view and click metrics for a specific offer over the requested time interval. The offer must belong to the account that owns the API key.
Header: x-api-key: your_api_key_here
Method: GET
offer_id integer, required
Path parameter.
Example: 45
interval string, required
One of 24h, 7d, 30d, 60d, 90d or all.
200 — Analytics returned successfully.
401 — Invalid API key, disabled account, or offer not owned by this account.
500 — Internal server error.
import requests
url = "https://ads.agilis.dating/offers/45/analytics"
headers = {"x-api-key": "your_api_key_here"}
params = {"interval": "30d"}
response = requests.get(url, headers=headers, params=params)
print(response.json()){
"offer_id": 45,
"interval": "30d",
"start_datetime": "2026-07-10T08:00:00Z",
"end_datetime": "2026-08-09T08:00:00Z",
"status": "Active",
"metrics": {
"views": 4789,
"clicks": 1032
}
}
Offer Price Estimate
Calculates the price of an offer in Ad Units before it is created, based on the duration and the number of Agilis users within the target radius.
Header: x-api-key: your_api_key_here
Content-Type: application/json
Method: POST
start_datetime string, required
ISO 8601, UTC.
Example: "2026-07-10T08:00:00Z"
end_datetime string, required
ISO 8601, UTC.
Example: "2026-07-12T20:30:00Z"
location array, required
Latitude and longitude.
Example: [51.5074, -0.1278]
radius integer, required
Miles, minimum 5, maximum 30.
Example: 10
S1 — Estimate calculated successfully.
E1 — One or more inputs are missing or invalid.
E2 — A date/time is not in valid ISO 8601 format.
import requests
url = "https://ads.agilis.dating/offers/price-estimate"
headers = {"x-api-key": "your_api_key_here"}
payload = {
"start_datetime": "2026-07-10T08:00:00Z",
"end_datetime": "2026-07-12T20:30:00Z",
"location": [51.5074, -0.1278],
"radius": 10
}
response = requests.post(url, headers=headers, json=payload)
print(response.json()){
"return_code": "S1",
"price_estimate": 45.50
}