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
Fetch Interests GET /interests (only if targeting by interest)
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 |
1b Fetch Interests Optional
Offers can optionally be targeted at users who have selected particular interests. Retrieve the available interests to obtain their ids. Skip this step if you do not intend to target by interest.
Endpoint: GET /interests
curl -X GET "https://ads.agilis.dating/interests" \
-H "x-api-key: YOUR_API_KEY_HERE"
Successful response — HTTP 200
{
"interests": [
{ "interest_id": 3, "interest_name": "Cooking" },
{ "interest_id": 7, "interest_name": "Fitness" },
{ "interest_id": 12, "interest_name": "Live music" }
]
}
Use the interest_id values in the optional interests array 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
Find out what the offer will cost before committing: how many Ad Units it uses, and what those units cost in money. This is especially important if you intend to pay from balance (payment method B), so you can confirm the balance is sufficient.
Endpoint: POST /offers/price-estimate
Price formula: Ad Units = duration in hours × number of matching Agilis users, rounded to 2 decimal places. Cost = Ad Units × the Ad Unit price, rounded to 2 decimal places.
The Ad Unit price is set in pounds sterling: £0.001. In any other currency it is £0.001 × that day's European Central Bank reference rate for the currency, rounded to 8 decimal places. For example, at 1.3412 US dollars to the pound one Ad Unit costs US$0.001341. Rates are refreshed each working day.
Your billing currency. Every organisation is billed in one currency, chosen when it registers. Your balance, every payment, every refund of Unused Time and every invoice are in it, and every price the API quotes or charges is in it. There is no currency parameter: you cannot choose a different currency per offer, and the billing currency cannot be changed through the API: once your organisation is approved, only Agilis support can change it.
Ad Units and money are different things. price_estimate is a number of Ad Units. Your balance, and the amount an offer deducts from it, is money in your billing currency. Compare your balance with estimated_cost, never with price_estimate: for an account in pounds the two differ by a factor of 100.
A user matches when they are within the target radius and satisfy any gender and interests targeting you supply. Only active, non-banned accounts are counted. Because targeting narrows the audience, it also lowers the price — pass the same gender and interests values you intend to use in Step 3, or the estimate will be higher than the amount actually charged.
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": "2027-08-01T09:00:00Z",
"end_datetime": "2027-08-31T23:59:00Z",
"location": [51.5074, -0.1278],
"radius": 10,
"gender": ["Male", "Female"],
"interests": [3, 7]
}'
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
start_datetime |
string (ISO 8601) | Yes | ISO 8601, UTC. Now or later: a start more than 60 seconds before now (UTC) is refused with E1. Example: "2027-07-10T08:00:00Z" |
end_datetime |
string (ISO 8601) | Yes | ISO 8601, UTC. Example: "2027-07-12T20:30:00Z" |
location |
array of 2 numbers | Yes | Latitude and longitude. Example: [51.5074, -0.1278] |
radius |
integer | Yes | Miles, minimum 5, maximum 30. Example: 10 |
gender |
array of strings | No | One or more of Male, Female, Non-binary, All. Omit, or include All, to reach every gender. Example: ["Male", "Female"] |
interests |
array of integers | No | Interest ids from GET /interests. A user matches if they share any of them. Omit to apply no interest filter. Example: [3, 7] |
environment |
string | No | TEST or LIVE. Defaults to your API key's own mode. If sent, it must be your key's mode (HTTP 403 otherwise). The Ad Unit price is the same in both. |
Successful response — HTTP 200, S1
{
"return_code": "S1",
"price_estimate": 1337669.67,
"matching_users": 1820,
"ad_units": 1337669.67,
"ad_unit_price": 0.001,
"estimated_cost": 1337.67,
"currency": "GBP",
"exchange_rate_per_gbp": 1,
"exchange_rate_date": null
}
The same offer for an organisation billed in US dollars, on a day when the rate was 1.3412:
{
"return_code": "S1",
"price_estimate": 1337669.67,
"matching_users": 1820,
"ad_units": 1337669.67,
"ad_unit_price": 0.001341,
"estimated_cost": 1794.08,
"currency": "USD",
"exchange_rate_per_gbp": 1.3412,
"exchange_rate_date": "2027-09-29"
}
| Field | Meaning |
|---|---|
price_estimate | The offer's size in Ad Units. Not money. Here: 1,820 users × 734.98 hours |
ad_units | The same figure under a clearer name. price_estimate is kept for existing integrations |
matching_users | The number of Agilis users the offer would reach with the targeting supplied. 0 means the offer cannot be created as specified: widen the radius or broaden the targeting |
ad_unit_price | The price of one Ad Unit in currency, to 6 decimal places |
estimated_cost | The money the offer will cost, in currency: the amount deducted from your balance, or charged to your card (plus VAT where it applies). This is the figure to compare with your balance |
currency | Your organisation's billing currency: the currency of ad_unit_price and estimated_cost |
exchange_rate_per_gbp | The European Central Bank reference rate the price was converted at: units of currency per pound sterling. 1 for GBP |
exchange_rate_date | The date of that rate, YYYY-MM-DD. null for GBP |
If your billing currency cannot be billed right now, the estimate still returns S1 with the Ad Unit figures, and ad_unit_price, estimated_cost, exchange_rate_per_gbp and exchange_rate_date are null. That happens when the currency's exchange rate is out of date (try again later), or when your organisation registered in a currency Agilis does not bill in (an Org Admin can choose a supported billing currency in the Ad Portal). POST /offers refuses both cases with E6.
Error responses
| Code | HTTP | Meaning |
|---|---|---|
| S1 | 200 | Estimate calculated successfully. |
| E1 | 400 | One or more inputs are missing or invalid, or start_datetime is in the past (more than 60 seconds before now). |
| E2 | 400 | A date/time is not in valid ISO 8601 format. |
| E1 | 403 | environment is not your API key's mode. |
| 401 | 401 | 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 |
|---|---|---|---|
Idempotency-Key header |
string | Yes | A unique value you generate per offer (for example 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. Missing: HTTP 400 / E1. |
category_id |
integer | Yes | A valid category ID from the Category List API. Example: 1 |
title |
string | Yes | Up to 40 characters. Example: "20% off summer sale" |
description |
string | Yes | Up to 500 characters. Example: "Discount valid through July" |
terms_and_conditions |
string | No | Up to 500 characters. Example: "Valid online only." |
advertised_name |
string | No | The business name members see on the offer instead of your organisation's trading name — for example when an agency advertises for a client. Up to 100 characters; must not start with http://, https:// or www.; no HTML or line breaks. Omit or send empty to show your trading name. You must be authorised by the business you name. Invoices and analytics stay in your organisation's name. Example: "Brighton Bakehouse" |
url |
string (URL) | Yes | Your offer page. Example: "https://advertiseroffer.com" |
image |
file | Yes | JPG, PNG or GIF, maximum 5 MB, sent as a binary file stream. Recommended size: 1080x1080px |
location[] |
array of 2 numbers | Yes | Latitude and longitude as two values. Example: [51.5074, -0.1278] |
radius |
integer | Yes | Radius in miles, minimum 5, maximum 30. Example: 10 |
gender[] |
array of strings | No | Target specific genders. One or more of Male, Female, Non-binary, All. Omitting the field, or including All, reaches every gender. Narrowing this reduces the audience and therefore the price. Example: ["Male", "Female"] |
interests[] |
array of integers | No | Target users by interest, using ids from GET /interests. A user matches if they share any of the interests supplied. Omitting the field applies no interest filter. Narrowing this reduces the audience and therefore the price. Example: [3, 7] |
start_datetime |
string (ISO 8601) | Yes | ISO 8601, UTC. Now or later: a start more than 60 seconds before now (UTC) is refused with E1. Example: "2027-07-10T08:00:00Z" |
expiry_datetime |
string (ISO 8601) | Yes | ISO 8601, UTC; at least 60 seconds after start_datetime. Example: "2027-07-12T20:30:00Z" |
payment_method |
string | Yes | E = Stripe payment link by email; B = deduct from organisation balance (the balance of the offer's mode: TEST and LIVE balances are separate). For E, an organisation registered in the UK is charged VAT at 20% on top, as a separate line on the Stripe payment page. The offer's price and balance stay net of VAT. |
environment |
string | Yes | TEST or LIVE, in capitals. Must be your API key's own mode: a pk_live_ key creates LIVE offers only, a pk_test_ key TEST offers only. |
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 "location[]=51.5074" \
-F "location[]=-0.1278" \
-F "radius=10" \
-F "gender[]=Male" \
-F "gender[]=Female" \
-F "interests[]=3" \
-F "interests[]=7" \
-F "start_datetime=2027-08-01T09:00:00Z" \
-F "expiry_datetime=2027-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_...",
"currency": "GBP"
}
currency is your organisation's billing currency, the currency the card is charged in.
Direct your user to this Stripe Checkout page. Your user enters their card details and completes payment on Stripe's hosted page. The page shows one line for the offer, at the offer's price (the estimated_cost from Step 2), in your billing currency.
Minimum card payment: Stripe will not take a card payment below its minimum for the currency (for example £0.30, US$0.50 or €0.50). A card offer priced below it is refused with E1, and the message names the minimum: widen the audience, lengthen the offer, or pay from your balance.
VAT: for an organisation registered in the UK, the Stripe page adds VAT at 20% as a separate line, so the card is charged the offer's price plus VAT. Organisations registered anywhere else are charged no VAT. The offer's price, and any Unused Time later credited to your balance, are always net of VAT.
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 your organisation's balance is at least the estimated_cost returned in Step 2. That is the money figure; do not compare the balance with price_estimate, which is a number of Ad Units. 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.
The balance used is the one for your API key's mode. TEST and LIVE balances are separate: a TEST offer is paid from the TEST balance, a LIVE offer from the LIVE balance, and a TEST top-up can never pay for a LIVE offer. No VAT is added to a balance payment; where VAT applies, it was charged when the balance was topped up.
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 "location[]=51.5074" \
-F "location[]=-0.1278" \
-F "radius=10" \
-F "gender[]=Male" \
-F "gender[]=Female" \
-F "interests[]=3" \
-F "interests[]=7" \
-F "start_datetime=2027-08-01T09:00:00Z" \
-F "expiry_datetime=2027-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
}
The price of the Ad Units, in your organisation's billing currency, is deducted from your balance and your organisation's balance, and Org Admins are notified by email in both cases.
Create Offer error responses (both payment paths)
| Code | HTTP | Meaning |
|---|---|---|
| S1 | 200 | Offer created: payment link issued (E), or Active/Scheduled if paid from balance (B). |
| E1 | 400 | One or more inputs are missing or invalid: an invalid datetime, a duration under 60 seconds, a radius outside 5 to 30 miles, an oversized image or wrong file type, an unrecognised gender value, an unknown interests id, or an advertised_name over 100 characters, starting with http://, https:// or www., or containing HTML or a line break. |
| E1 | 400 | start_datetime is more than 60 seconds before now (UTC): nothing created or charged. Message: "start_datetime cannot be in the past. Use the current time or later." |
| E1 | 400 | A card payment (payment_method=E) below Stripe's minimum for your currency: nothing created; the message names the minimum. |
| E1 | 400 | The Idempotency-Key header is missing. Message: "Idempotency-Key header is required." |
| E2 | 200 | The image failed automated content moderation: nothing created. Choose a different image. |
| E3 | 200 | The organisation's balance is less than the offer's cost (payment_method=B only): top up and retry. |
| E4 | 400 | Offer quota limit exceeded: the user or organisation has reached its maximum number of offers. |
| E5 | 400 | No Agilis users match the targeting supplied: nothing created. Widen the radius or broaden the gender or interests targeting. |
| E6 | 400 | Your billing currency cannot be billed right now: nothing created or charged. The message is either Your organisation's currency (XYZ) is not one Agilis bills in. An Org Admin can choose a supported billing currency in the Ad Portal. You have not been charged. or Prices in XYZ are temporarily unavailable because the exchange rate is out of date. Please try again later. You have not been charged., with your currency code in place of XYZ. |
| E1 | 403 | environment is not your API key's mode: nothing created or charged. Use a key created in that mode. |
| E9 | 403 | Your organisation has not accepted the current Agilis Advertising Terms and Conditions: nothing created or charged. An Org Admin must accept them in the Ad Portal (adportal.agilis.dating); then retry with the same Idempotency-Key. |
| 409 | 409 | The Idempotency-Key was reused while the first request is still being processed: retry shortly. |
| 422 | 422 | The Idempotency-Key was reused with a different payload. |
| 401 | 401 | Authentication failure. |
| 500 | 500 | Internal 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). It must be your API key's own mode: naming the other mode returns HTTP 403.
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 closes its Stripe payment page first; deleting a paid offer credits its Unused Time to your balance. 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).
- A key is shown once. The Ad Portal shows the full key only when you create it (or roll it). Copy it then and store it securely: Agilis does not keep a copy, so it cannot be shown again. The key list shows only its last four characters. If a key is lost or may have been exposed, use Roll key: you get a new key, shown once, and the old one stops working immediately.
- Each key works in one mode only, and this is enforced: the mode the Ad Portal was in (its test/live mode toggle) when the key was created. A
pk_live_key is a LIVE key and apk_test_key is a TEST key. To work in both modes, create one key in each. - A request whose
environmentnames the other mode is refused with HTTP 403 before anything is created or charged. The message names your key's mode:{"return_code": "E1", "status": "Error", "code": 403, "message": "This is a LIVE API key, so it can only be used with environment=LIVE. Use a TEST key (created in the Ad Portal in TEST MODE) for TEST requests."} - Offers of the other mode are treated as not found: pause, resume, analytics and delete return HTTP 401 (
E1, "Offer not found" on delete), exactly as for an offer that does not exist. - A key with no recorded mode returns HTTP 401 with
{"return_code": "E1", "message": "This API key has no environment. Create a new key in the Ad Portal."}. Create a new key. - 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.
- Calls are recorded for 90 days. Every request made with your API key, including refused ones, is recorded with the key, the endpoint (its route, such as
offers/{id}/pause, without ids or the query string), the HTTP status, thereturn_code, the response time and the time of the call. The request body, the query string and your IP address are not recorded. Records are deleted after 90 days. Recording does not change any request or response.
Rate limits
- 60 requests a minute per client IP address, across every endpoint.
- Every response carries
X-RateLimit-Limit(the requests allowed in the current minute) andX-RateLimit-Remaining(how many are left). - Over the limit, the API returns HTTP 429 with a
Retry-Afterheader: the number of seconds to wait before trying again. Retry a refusedPOST /offerswith the sameIdempotency-Key.
Environment Testing
Use a TEST key (created while the Ad Portal is in TEST mode) and set environment=TEST (in capitals) in the Create Offer request to create offers in a sandbox. Test offers are only returned when calling GET /offers?environment=test with a TEST key, do not appear to real Agilis users in the app, and follow the same creation, payment, and management flow as live offers. Card payments for TEST offers run in Stripe's test mode, and balance payments use the separate TEST balance. When your integration is ready, switch to a LIVE key: the Ad Portal's test/live mode toggle decides which kind of key you create.
Category List
GET
/categories
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
Response fields
category_idinteger- Unique ID of the category. Example: 1
category_namestring- Name of the category. Example: "Retail"
Response codes
| Code | HTTP | Meaning |
|---|---|---|
| 200 | 200 | Categories retrieved successfully. |
| 401 | 401 | Invalid API key or disabled account. |
| 500 | 500 | Internal server error. |
/categories
curl -X GET "https://ads.agilis.dating/categories" \
-H "x-api-key: YOUR_API_KEY_HERE"
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())
const axios = require('axios');
axios({
method: 'get',
url: 'https://ads.agilis.dating/categories',
headers: {
'x-api-key': 'YOUR_API_KEY_HERE'
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/categories");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"categories": [
{
"category_id": 1,
"category_name": "Retail"
},
{
"category_id": 2,
"category_name": "Food & Beverage"
}
]
}
Interest List
GET
/interests
Returns the interests available for offer targeting. Call it before creating an offer if you intend to use the optional interests array, which restricts the offer to users who have selected at least one of the interests supplied.
- Header
x-api-key: YOUR_API_KEY_HERE
Response fields
interest_idinteger- Unique ID of the interest. Example: 3
interest_namestring- Name of the interest. Example: "Cooking"
Response codes
| Code | HTTP | Meaning |
|---|---|---|
| 200 | 200 | Interests retrieved successfully. |
| 401 | 401 | Invalid API key or disabled account. |
| 500 | 500 | Internal server error. |
/interests
curl -X GET "https://ads.agilis.dating/interests" \
-H "x-api-key: YOUR_API_KEY_HERE"
import requests
url = "https://ads.agilis.dating/interests"
headers = {
"x-api-key": "YOUR_API_KEY_HERE",
}
response = requests.get(url, headers=headers)
print(response.json())
const axios = require('axios');
axios({
method: 'get',
url: 'https://ads.agilis.dating/interests',
headers: {
'x-api-key': 'YOUR_API_KEY_HERE'
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/interests");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"interests": [
{
"interest_id": 3,
"interest_name": "Cooking"
},
{
"interest_id": 7,
"interest_name": "Fitness"
}
]
}
Offer Price Estimate
POST
/offers/price-estimate
Calculates what an offer will cost before it is created: its size in Ad Units (duration in hours × the number of Agilis users it would reach) and what those units cost in money. Only active, non-banned accounts are counted. Pass the same gender and interests values you intend to create the offer with, otherwise the estimate will be higher than the amount actually charged.
price_estimate is a number of Ad Units, not money. estimated_cost is the money the offer will cost, and is the figure to compare with your balance.
There is no currency parameter: the estimate is always in your organisation's billing currency, named in the response's currency field.
- Header
x-api-key: YOUR_API_KEY_HERE- Content-Type
application/json
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
start_datetime |
string (ISO 8601) | Yes | ISO 8601, UTC. Now or later: a start more than 60 seconds before now (UTC) is refused with E1. Example: "2027-07-10T08:00:00Z" |
end_datetime |
string (ISO 8601) | Yes | ISO 8601, UTC. Example: "2027-07-12T20:30:00Z" |
location |
array of 2 numbers | Yes | Latitude and longitude. Example: [51.5074, -0.1278] |
radius |
integer | Yes | Miles, minimum 5, maximum 30. Example: 10 |
gender |
array of strings | No | One or more of Male, Female, Non-binary, All. Omit, or include All, to reach every gender. Example: ["Male", "Female"] |
interests |
array of integers | No | Interest ids from GET /interests. A user matches if they share any of them. Omit to apply no interest filter. Example: [3, 7] |
environment |
string | No | TEST or LIVE. Defaults to your API key's own mode. If sent, it must be your key's mode (HTTP 403 otherwise). The Ad Unit price is the same in both. |
Response fields
price_estimatenumber- Size of the offer in Ad Units. Not money.
ad_unitsnumber- The same figure, under a clearer name.
price_estimateis kept for existing integrations. matching_usersinteger- Agilis users the offer would reach.
0means it cannot be created as specified: widen the radius or broaden the targeting. ad_unit_pricenumber|null- Price of one Ad Unit in
currency, to 6 decimal places: £0.001 × the day's European Central Bank rate. estimated_costnumber|null- The money the offer will cost. Compare this with your balance.
currencystring- Your organisation's billing currency.
exchange_rate_per_gbpnumber|null- The rate used, per pound sterling.
1for GBP. exchange_rate_datestring|null- The rate's date (
YYYY-MM-DD);nullfor GBP.
If your billing currency cannot be billed right now (not one Agilis bills in, or its rate is out of date), ad_unit_price, estimated_cost, exchange_rate_per_gbp and exchange_rate_date are null; the Ad Unit figures are still returned, and POST /offers would refuse the offer with E6.
Return codes
| Code | HTTP | Meaning |
|---|---|---|
| S1 | 200 | Estimate calculated successfully. |
| E1 | 400 | One or more inputs are missing or invalid, or start_datetime is in the past (more than 60 seconds before now). |
| E2 | 400 | A date/time is not in valid ISO 8601 format. |
| E1 | 403 | environment is not your API key's mode. |
| 401 | 401 | Authentication failure. |
/offers/price-estimate
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": "2027-07-10T08:00:00Z",
"end_datetime": "2027-07-12T20:30:00Z",
"location": [51.5074, -0.1278],
"radius": 10,
"gender": ["Male", "Female"],
"interests": [3, 7]
}'
import requests
url = "https://ads.agilis.dating/offers/price-estimate"
headers = {
"x-api-key": "YOUR_API_KEY_HERE",
}
payload = {
"start_datetime": "2027-07-10T08:00:00Z",
"end_datetime": "2027-07-12T20:30:00Z",
"location": [51.5074, -0.1278],
"radius": 10,
"gender": ["Male", "Female"],
"interests": [3, 7]
}
response = requests.post(url, headers=headers, json=payload)
print(response.json())
const axios = require('axios');
axios({
method: 'post',
url: 'https://ads.agilis.dating/offers/price-estimate',
headers: {
'x-api-key': 'YOUR_API_KEY_HERE'
},
data: {
"start_datetime": "2027-07-10T08:00:00Z",
"end_datetime": "2027-07-12T20:30:00Z",
"location": [51.5074, -0.1278],
"radius": 10,
"gender": ["Male", "Female"],
"interests": [3, 7]
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/offers/price-estimate");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"start_datetime" => "2027-07-10T08:00:00Z",
"end_datetime" => "2027-07-12T20:30:00Z",
"location" => [51.5074, -0.1278],
"radius" => 10,
"gender" => ["Male", "Female"],
"interests" => [3, 7],
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"return_code": "S1",
"price_estimate": 110110,
"matching_users": 1820,
"ad_units": 110110,
"ad_unit_price": 0.001,
"estimated_cost": 110.11,
"currency": "GBP",
"exchange_rate_per_gbp": 1,
"exchange_rate_date": null
}
Create Offer
POST
/offers
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: if it fails, nothing is created and nothing is charged.
Paying by Stripe link (payment_method=E): the offer is created in Awaiting Payment and the response carries a payment_url. Stripe's checkout.session.completed webhook activates it: Active if its start has passed, Scheduled if not. An organisation registered in the UK pays VAT at 20% on top, as a separate line on the Stripe page. Stripe will not take a card payment below its minimum for the currency (for example £0.30, US$0.50 or €0.50): such an offer is refused with E1.
Paying from balance (payment_method=B): the offer's price is deducted at once, from the balance of the offer's own mode (TEST and LIVE balances are separate). Compare your balance with estimated_cost from the price estimate, never price_estimate.
There is no currency parameter: every offer is priced and charged in your organisation's billing currency, named in the response.
- Header
x-api-key: YOUR_API_KEY_HERE- Content-Type
multipart/form-data
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
Idempotency-Key header |
string | Yes | A unique value you generate per offer (for example 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. Missing: HTTP 400 / E1. |
category_id |
integer | Yes | A valid category ID from the Category List API. Example: 1 |
title |
string | Yes | Up to 40 characters. Example: "20% off summer sale" |
description |
string | Yes | Up to 500 characters. Example: "Discount valid through July" |
terms_and_conditions |
string | No | Up to 500 characters. Example: "Valid online only." |
advertised_name |
string | No | The business name members see on the offer instead of your organisation's trading name — for example when an agency advertises for a client. Up to 100 characters; must not start with http://, https:// or www.; no HTML or line breaks. Omit or send empty to show your trading name. You must be authorised by the business you name. Invoices and analytics stay in your organisation's name. Example: "Brighton Bakehouse" |
url |
string (URL) | Yes | Your offer page. Example: "https://advertiseroffer.com" |
image |
file | Yes | JPG, PNG or GIF, maximum 5 MB, sent as a binary file stream. Recommended size: 1080x1080px |
location[] |
array of 2 numbers | Yes | Latitude and longitude as two values. Example: [51.5074, -0.1278] |
radius |
integer | Yes | Radius in miles, minimum 5, maximum 30. Example: 10 |
gender[] |
array of strings | No | Target specific genders. One or more of Male, Female, Non-binary, All. Omitting the field, or including All, reaches every gender. Narrowing this reduces the audience and therefore the price. Example: ["Male", "Female"] |
interests[] |
array of integers | No | Target users by interest, using ids from GET /interests. A user matches if they share any of the interests supplied. Omitting the field applies no interest filter. Narrowing this reduces the audience and therefore the price. Example: [3, 7] |
start_datetime |
string (ISO 8601) | Yes | ISO 8601, UTC. Now or later: a start more than 60 seconds before now (UTC) is refused with E1. Example: "2027-07-10T08:00:00Z" |
expiry_datetime |
string (ISO 8601) | Yes | ISO 8601, UTC; at least 60 seconds after start_datetime. Example: "2027-07-12T20:30:00Z" |
payment_method |
string | Yes | E = Stripe payment link by email; B = deduct from organisation balance (the balance of the offer's mode: TEST and LIVE balances are separate). For E, an organisation registered in the UK is charged VAT at 20% on top, as a separate line on the Stripe payment page. The offer's price and balance stay net of VAT. |
environment |
string | Yes | TEST or LIVE, in capitals. Must be your API key's own mode: a pk_live_ key creates LIVE offers only, a pk_test_ key TEST offers only. |
Return codes
| Code | HTTP | Meaning |
|---|---|---|
| S1 | 200 | Offer created: payment link issued (E), or Active/Scheduled if paid from balance (B). |
| E1 | 400 | One or more inputs are missing or invalid: an invalid datetime, a duration under 60 seconds, a radius outside 5 to 30 miles, an oversized image or wrong file type, an unrecognised gender value, an unknown interests id, or an advertised_name over 100 characters, starting with http://, https:// or www., or containing HTML or a line break. |
| E1 | 400 | start_datetime is more than 60 seconds before now (UTC): nothing created or charged. Message: "start_datetime cannot be in the past. Use the current time or later." |
| E1 | 400 | A card payment (payment_method=E) below Stripe's minimum for your currency: nothing created; the message names the minimum. |
| E1 | 400 | The Idempotency-Key header is missing. Message: "Idempotency-Key header is required." |
| E2 | 200 | The image failed automated content moderation: nothing created. Choose a different image. |
| E3 | 200 | The organisation's balance is less than the offer's cost (payment_method=B only): top up and retry. |
| E4 | 400 | Offer quota limit exceeded: the user or organisation has reached its maximum number of offers. |
| E5 | 400 | No Agilis users match the targeting supplied: nothing created. Widen the radius or broaden the gender or interests targeting. |
| E6 | 400 | Your billing currency cannot be billed right now: nothing created or charged. The message is either Your organisation's currency (XYZ) is not one Agilis bills in. An Org Admin can choose a supported billing currency in the Ad Portal. You have not been charged. or Prices in XYZ are temporarily unavailable because the exchange rate is out of date. Please try again later. You have not been charged., with your currency code in place of XYZ. |
| E1 | 403 | environment is not your API key's mode: nothing created or charged. Use a key created in that mode. |
| E9 | 403 | Your organisation has not accepted the current Agilis Advertising Terms and Conditions: nothing created or charged. An Org Admin must accept them in the Ad Portal (adportal.agilis.dating); then retry with the same Idempotency-Key. |
| 409 | 409 | The Idempotency-Key was reused while the first request is still being processed: retry shortly. |
| 422 | 422 | The Idempotency-Key was reused with a different payload. |
| 401 | 401 | Authentication failure. |
| 500 | 500 | Internal server error. |
/offers
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 "category_id=1" \
-F "title=20% off summer sale" \
-F "description=Discount valid through July" \
-F "terms_and_conditions=Valid online only." \
-F "url=https://advertiseroffer.com" \
-F "location[]=51.5074" \
-F "location[]=-0.1278" \
-F "radius=10" \
-F "gender[]=Male" \
-F "gender[]=Female" \
-F "interests[]=3" \
-F "interests[]=7" \
-F "start_datetime=2027-07-10T08:00:00Z" \
-F "expiry_datetime=2027-07-12T20:30:00Z" \
-F "payment_method=E" \
-F "environment=LIVE" \
-F "image=@/path/to/offer.jpg"
import uuid
import requests
url = "https://ads.agilis.dating/offers"
headers = {
"x-api-key": "YOUR_API_KEY_HERE",
# One per request; send the same value again to retry it safely.
"Idempotency-Key": str(uuid.uuid4()),
}
data = [
("category_id", "1"),
("title", "20% off summer sale"),
("description", "Discount valid through July"),
("terms_and_conditions", "Valid online only."),
("url", "https://advertiseroffer.com"),
("location[]", "51.5074"),
("location[]", "-0.1278"),
("radius", "10"),
("gender[]", "Male"),
("gender[]", "Female"),
("interests[]", "3"),
("interests[]", "7"),
("start_datetime", "2027-07-10T08:00:00Z"),
("expiry_datetime", "2027-07-12T20:30:00Z"),
("payment_method", "E"),
("environment", "LIVE"),
]
with open("/path/to/offer.jpg", "rb") as f:
response = requests.post(url, headers=headers, data=data, files={"image": ("offer.jpg", f, "image/jpeg")})
print(response.json())
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const { randomUUID } = require('crypto');
const form = new FormData();
form.append('category_id', '1');
form.append('title', '20% off summer sale');
form.append('description', 'Discount valid through July');
form.append('terms_and_conditions', 'Valid online only.');
form.append('url', 'https://advertiseroffer.com');
form.append('location[]', '51.5074');
form.append('location[]', '-0.1278');
form.append('radius', '10');
form.append('gender[]', 'Male');
form.append('gender[]', 'Female');
form.append('interests[]', '3');
form.append('interests[]', '7');
form.append('start_datetime', '2027-07-10T08:00:00Z');
form.append('expiry_datetime', '2027-07-12T20:30:00Z');
form.append('payment_method', 'E');
form.append('environment', 'LIVE');
form.append('image', fs.createReadStream('/path/to/offer.jpg'));
axios({
method: 'post',
url: 'https://ads.agilis.dating/offers',
headers: {
...form.getHeaders(),
'x-api-key': 'YOUR_API_KEY_HERE',
// One per request; send the same value again to retry it safely.
'Idempotency-Key': randomUUID()
},
data: form
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/offers");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
// One per request; send the same value again to retry it safely.
"Idempotency-Key: " . bin2hex(random_bytes(16)),
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
"category_id" => "1",
"title" => "20% off summer sale",
"description" => "Discount valid through July",
"terms_and_conditions" => "Valid online only.",
"url" => "https://advertiseroffer.com",
"location[0]" => "51.5074",
"location[1]" => "-0.1278",
"radius" => "10",
"gender[0]" => "Male",
"gender[1]" => "Female",
"interests[0]" => "3",
"interests[1]" => "7",
"start_datetime" => "2027-07-10T08:00:00Z",
"expiry_datetime" => "2027-07-12T20:30:00Z",
"payment_method" => "E",
"environment" => "LIVE",
"image" => new CURLFile("/path/to/offer.jpg", "image/jpeg"),
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"return_code": "S1",
"offer_id": 45,
"status": "Awaiting Payment",
"payment_url": "https://checkout.stripe.com/c/pay/cs_live_...",
"currency": "GBP"
}
// payment_method=B, start in the future (HTTP 200, S1):
// { "return_code": "S1", "offer_id": 46, "status": "Scheduled", "payment_url": null }
View Offers
GET
/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
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
environment query |
string | Yes | test or live (case-insensitive). Must be your API key's own mode. Example: /offers?environment=test |
limit query |
integer | No | Page size. Default 20, maximum 100. |
cursor query |
string | No | Opaque token from the previous page's next_cursor. Omit for the first page. |
offer_status query |
string | No | Lifecycle filter: awaiting_payment, active, paused or ended (case-insensitive). |
created_after query |
string (ISO 8601) | No | ISO 8601, UTC. Offers created at or after this time. |
created_before query |
string (ISO 8601) | No | ISO 8601, UTC. Offers created at or before this time. |
Response fields
Each offer in data contains:
offer_idinteger- The offer's id.
title, category, description, terms_and_conditions, urlstring- As created.
advertised_namestring|null- The business name members see;
nullwhen your trading name is shown. imagestring- Signed CloudFront URL of the image.
latitude, longitude, radiusnumber- Where the offer is shown.
genderarray- For example
["Male","Female"]; empty when the offer targets every gender. interestsarray{interest_id, interest_name}objects; empty when no interest targeting was applied.start_datetime, expiry_datetime, creation_datetimestring- ISO 8601, UTC.
offer_statusstring- Awaiting Payment, Active, Scheduled, Paused or Ended.
statusstringenabledordisabled: whether an Admin has disabled the offer.
Response codes
| Code | HTTP | Meaning |
|---|---|---|
| 200 | 200 | Page returned (empty data array if none match). |
| 400 | 400 | Invalid query parameter (limit, cursor, offer_status or date). |
| 401 | 401 | Invalid API key or disabled account. |
| 403 | 403 | environment is not your API key's mode. |
| 500 | 500 | Internal server error. |
/offers
curl -X GET "https://ads.agilis.dating/offers?environment=test&limit=20" \
-H "x-api-key: YOUR_API_KEY_HERE"
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())
const axios = require('axios');
axios({
method: 'get',
url: 'https://ads.agilis.dating/offers',
headers: {
'x-api-key': 'YOUR_API_KEY_HERE'
},
params: {
"environment": "test",
"limit": "20"
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/offers?" . http_build_query(["environment" => "test", "limit" => "20"]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"data": [
{
"offer_id": 45,
"title": "20% off summer sale",
"category": "Retail",
"description": "Discount valid through July",
"terms_and_conditions": "Valid online only.",
"advertised_name": null,
"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"],
"interests": [
{
"interest_id": 3,
"interest_name": "Cooking"
},
{
"interest_id": 7,
"interest_name": "Fitness"
}
],
"start_datetime": "2027-07-10T08:00:00Z",
"expiry_datetime": "2027-07-12T20:30:00Z",
"creation_datetime": "2027-06-01T10:00:00Z",
"offer_status": "Active",
"status": "enabled"
}
],
"pagination": {
"limit": 20,
"count": 1,
"has_more": false,
"next_cursor": null
}
}
Pause Offer
POST
/offers/{offer_id}/pause
Pauses a running offer. The offer must belong to the account that owns the API key and be in the key's mode (TEST or LIVE). Any other offer returns HTTP 401 with E1. Time spent paused is Unused Time: it is credited to your balance if you later delete the offer.
- Header
x-api-key: YOUR_API_KEY_HERE
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
offer_id path |
integer | Yes | Path parameter. Example: 45 |
Return codes
| Code | HTTP | Meaning |
|---|---|---|
| S1 | 200 | Offer paused successfully. |
| E1 | 401 | Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode. |
| E2 | 200 | Offer has already ended. |
| E3 | 200 | Offer is already paused. |
| E5 | 200 | Offer is awaiting payment. |
/offers/{offer_id}/pause
curl -X POST "https://ads.agilis.dating/offers/45/pause" \
-H "x-api-key: YOUR_API_KEY_HERE"
import requests
url = "https://ads.agilis.dating/offers/45/pause"
headers = {
"x-api-key": "YOUR_API_KEY_HERE",
}
response = requests.post(url, headers=headers)
print(response.json())
const axios = require('axios');
axios({
method: 'post',
url: 'https://ads.agilis.dating/offers/45/pause',
headers: {
'x-api-key': 'YOUR_API_KEY_HERE'
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/offers/45/pause");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"return_code": "S1",
"message": "Offer has been paused."
}
Resume Offer
POST
/offers/{offer_id}/resume
Resumes a paused offer. The offer must belong to the account that owns the API key and be in the key's mode. Any other offer returns HTTP 401 with E1.
- Header
x-api-key: YOUR_API_KEY_HERE
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
offer_id path |
integer | Yes | Path parameter. Example: 45 |
Return codes
| Code | HTTP | Meaning |
|---|---|---|
| S2 | 200 | Offer resumed successfully. |
| E1 | 401 | Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode. |
| E2 | 200 | Offer has already ended. |
| E4 | 200 | Offer is already running. |
| E5 | 200 | Offer is awaiting payment. |
/offers/{offer_id}/resume
curl -X POST "https://ads.agilis.dating/offers/45/resume" \
-H "x-api-key: YOUR_API_KEY_HERE"
import requests
url = "https://ads.agilis.dating/offers/45/resume"
headers = {
"x-api-key": "YOUR_API_KEY_HERE",
}
response = requests.post(url, headers=headers)
print(response.json())
const axios = require('axios');
axios({
method: 'post',
url: 'https://ads.agilis.dating/offers/45/resume',
headers: {
'x-api-key': 'YOUR_API_KEY_HERE'
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/offers/45/resume");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"return_code": "S2",
"message": "Offer has been resumed."
}
View Offer Analytics
GET
/offers/{offer_id}/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 and be in the key's mode.
- Header
x-api-key: YOUR_API_KEY_HERE
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
offer_id path |
integer | Yes | Path parameter. Example: 45 |
interval query |
string | Yes | One of 24h, 7d, 30d, 60d, 90d or all. |
Response codes
| Code | HTTP | Meaning |
|---|---|---|
| 200 | 200 | Analytics returned successfully. |
| 401 | 401 | Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode. |
| 500 | 500 | Internal server error. |
/offers/{offer_id}/analytics
curl -X GET "https://ads.agilis.dating/offers/45/analytics?interval=30d" \
-H "x-api-key: YOUR_API_KEY_HERE"
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())
const axios = require('axios');
axios({
method: 'get',
url: 'https://ads.agilis.dating/offers/45/analytics',
headers: {
'x-api-key': 'YOUR_API_KEY_HERE'
},
params: {
"interval": "30d"
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/offers/45/analytics?" . http_build_query(["interval" => "30d"]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"offer_id": 45,
"interval": "30d",
"start_datetime": "2027-07-10T08:00:00Z",
"end_datetime": "2027-08-09T08:00:00Z",
"status": "Active",
"metrics": {
"views": 4789,
"clicks": 1032
}
}
Delete Offer
DELETE
/offers/{offer_id}
Deletes an offer belonging to the authenticated account, in your API key's mode (an offer of the other mode is not found).
Awaiting Payment: the offer's Stripe payment page is closed first, so it can no longer be paid. If Stripe reports that the payment has just been completed, or the page cannot be closed, nothing is deleted (E6, E7 below).
Paid offers (Active, Scheduled or Paused): the offer's Unused Time is credited to your balance in the offer's own mode. Unused Time is the part of the scheduled period in which the offer did not run: the time from the deletion to the scheduled end, plus any time it spent paused. An offer deleted before it starts is credited in full. The credit is net of VAT and goes to your balance, never back to a card.
- Header
x-api-key: YOUR_API_KEY_HERE
Parameters
| Field | Type | Required | Notes |
|---|---|---|---|
offer_id path |
integer | Yes | Path parameter. Example: 45 |
Return codes
| Code | HTTP | Meaning |
|---|---|---|
| S1 | 200 | Offer deleted successfully. |
| E1 | 401 | Invalid input, or "Offer not found": the offer does not belong to this account or is in the other mode. |
| E6 | 409 | The payment for this offer has just been completed, so it is no longer awaiting payment. Nothing has been deleted. Retrieve the offer again; if you still delete it, its Unused Time is credited to your balance. |
| E7 | 503 | The payment page could not be closed. Nothing has been deleted. Try again in a few minutes. |
/offers/{offer_id}
curl -X DELETE "https://ads.agilis.dating/offers/45" \
-H "x-api-key: YOUR_API_KEY_HERE"
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())
const axios = require('axios');
axios({
method: 'delete',
url: 'https://ads.agilis.dating/offers/45',
headers: {
'x-api-key': 'YOUR_API_KEY_HERE'
}
})
.then(response => console.log(response.data))
.catch(error => console.error(error.response ? error.response.data : error.message));
<?php
$ch = curl_init("https://ads.agilis.dating/offers/45");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: YOUR_API_KEY_HERE",
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
Example response
{
"return_code": "S1",
"message": "Offer has been deleted."
}
Return codes by endpoint
Every response carries a return_code: S1 (or S2 on resume) for success, E1 to E9 for errors. A code's meaning depends on the endpoint: E5 on Create Offer means no members match the targeting, but on Pause it means the offer is awaiting payment. Read the row for the endpoint you called. Each meaning links to that endpoint's own table.
| Code | Endpoint | HTTP | Meaning on that endpoint |
|---|---|---|---|
| S1 | POST /offers/price-estimate |
200 | Estimate calculated successfully. |
POST /offers |
200 | Offer created: payment link issued (E), or Active/Scheduled if paid from balance (B). |
|
POST /offers/{offer_id}/pause |
200 | Offer paused successfully. | |
DELETE /offers/{offer_id} |
200 | Offer deleted successfully. | |
| S2 | POST /offers/{offer_id}/resume |
200 | Offer resumed successfully. |
| E1 | POST /offers/price-estimate |
400 | One or more inputs are missing or invalid, or start_datetime is in the past (more than 60 seconds before now). |
POST /offers/price-estimate |
403 | environment is not your API key's mode. |
|
POST /offers |
400 | One or more inputs are missing or invalid: an invalid datetime, a duration under 60 seconds, a radius outside 5 to 30 miles, an oversized image or wrong file type, an unrecognised gender value, an unknown interests id, or an advertised_name over 100 characters, starting with http://, https:// or www., or containing HTML or a line break. |
|
POST /offers |
400 | start_datetime is more than 60 seconds before now (UTC): nothing created or charged. Message: "start_datetime cannot be in the past. Use the current time or later." |
|
POST /offers |
400 | A card payment (payment_method=E) below Stripe's minimum for your currency: nothing created; the message names the minimum. |
|
POST /offers |
400 | The Idempotency-Key header is missing. Message: "Idempotency-Key header is required." |
|
POST /offers |
403 | environment is not your API key's mode: nothing created or charged. Use a key created in that mode. |
|
POST /offers/{offer_id}/pause |
401 | Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode. | |
POST /offers/{offer_id}/resume |
401 | Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode. | |
DELETE /offers/{offer_id} |
401 | Invalid input, or "Offer not found": the offer does not belong to this account or is in the other mode. | |
| E2 | POST /offers/price-estimate |
400 | A date/time is not in valid ISO 8601 format. |
POST /offers |
200 | The image failed automated content moderation: nothing created. Choose a different image. | |
POST /offers/{offer_id}/pause |
200 | Offer has already ended. | |
POST /offers/{offer_id}/resume |
200 | Offer has already ended. | |
| E3 | POST /offers |
200 | The organisation's balance is less than the offer's cost (payment_method=B only): top up and retry. |
POST /offers/{offer_id}/pause |
200 | Offer is already paused. | |
| E4 | POST /offers |
400 | Offer quota limit exceeded: the user or organisation has reached its maximum number of offers. |
POST /offers/{offer_id}/resume |
200 | Offer is already running. | |
| E5 | POST /offers |
400 | No Agilis users match the targeting supplied: nothing created. Widen the radius or broaden the gender or interests targeting. |
POST /offers/{offer_id}/pause |
200 | Offer is awaiting payment. | |
POST /offers/{offer_id}/resume |
200 | Offer is awaiting payment. | |
| E6 | POST /offers |
400 | Your billing currency cannot be billed right now: nothing created or charged. The message is either Your organisation's currency (XYZ) is not one Agilis bills in. An Org Admin can choose a supported billing currency in the Ad Portal. You have not been charged. or Prices in XYZ are temporarily unavailable because the exchange rate is out of date. Please try again later. You have not been charged., with your currency code in place of XYZ. |
DELETE /offers/{offer_id} |
409 | The payment for this offer has just been completed, so it is no longer awaiting payment. Nothing has been deleted. Retrieve the offer again; if you still delete it, its Unused Time is credited to your balance. | |
| E7 | DELETE /offers/{offer_id} |
503 | The payment page could not be closed. Nothing has been deleted. Try again in a few minutes. |
| E9 | POST /offers |
403 | Your organisation has not accepted the current Agilis Advertising Terms and Conditions: nothing created or charged. An Org Admin must accept them in the Ad Portal (adportal.agilis.dating); then retry with the same Idempotency-Key. |