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-key request header.
  • Content-Type: application/json for JSON requests; multipart/form-data for 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

HTTPBodyMeaning
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

HTTPBodyMeaning
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

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

Offer Price Estimate parameters
FieldTypeRequiredNotes
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"
}
FieldMeaning
price_estimateThe offer's size in Ad Units. Not money. Here: 1,820 users × 734.98 hours
ad_unitsThe same figure under a clearer name. price_estimate is kept for existing integrations
matching_usersThe 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_priceThe price of one Ad Unit in currency, to 6 decimal places
estimated_costThe 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
currencyYour organisation's billing currency: the currency of ad_unit_price and estimated_cost
exchange_rate_per_gbpThe European Central Bank reference rate the price was converted at: units of currency per pound sterling. 1 for GBP
exchange_rate_dateThe 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

Offer Price Estimate: return codes
CodeHTTPMeaning
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

Create Offer parameters
FieldTypeRequiredNotes
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.
Idempotency (required): Every 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.

Note: If the user closes the Stripe page without completing payment, the offer remains in Awaiting Payment status. The platform still activates the offer the moment Stripe confirms payment — no action is required from you if the user later returns and completes the checkout.

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)

Create Offer: return codes
CodeHTTPMeaning
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

ValueMeaning
Awaiting PaymentCreated via method E; payment not yet received
ActiveLive and visible to Agilis users
ScheduledPaid for but start date is in the future
PausedTemporarily paused by the advertiser
EndedExpiry 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.

Pagination: 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 a pk_test_ key is a TEST key. To work in both modes, create one key in each.
  • A request whose environment names 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, the return_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) and X-RateLimit-Remaining (how many are left).
  • Over the limit, the API returns HTTP 429 with a Retry-After header: the number of seconds to wait before trying again. Retry a refused POST /offers with the same Idempotency-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_id integer
Unique ID of the category. Example: 1
category_name string
Name of the category. Example: "Retail"

Response codes

Category List: response codes
CodeHTTPMeaning
200 200 Categories retrieved successfully.
401 401 Invalid API key or disabled account.
500 500 Internal server error.
GET /categories
curl -X GET "https://ads.agilis.dating/categories" \
  -H "x-api-key: YOUR_API_KEY_HERE"

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_id integer
Unique ID of the interest. Example: 3
interest_name string
Name of the interest. Example: "Cooking"

Response codes

Interest List: response codes
CodeHTTPMeaning
200 200 Interests retrieved successfully.
401 401 Invalid API key or disabled account.
500 500 Internal server error.
GET /interests
curl -X GET "https://ads.agilis.dating/interests" \
  -H "x-api-key: YOUR_API_KEY_HERE"

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

Offer Price Estimate parameters
FieldTypeRequiredNotes
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_estimate number
Size of the offer in Ad Units. Not money.
ad_units number
The same figure, under a clearer name. price_estimate is kept for existing integrations.
matching_users integer
Agilis users the offer would reach. 0 means it cannot be created as specified: widen the radius or broaden the targeting.
ad_unit_price number|null
Price of one Ad Unit in currency, to 6 decimal places: £0.001 × the day's European Central Bank rate.
estimated_cost number|null
The money the offer will cost. Compare this with your balance.
currency string
Your organisation's billing currency.
exchange_rate_per_gbp number|null
The rate used, per pound sterling. 1 for GBP.
exchange_rate_date string|null
The rate's date (YYYY-MM-DD); null for 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

Offer Price Estimate: return codes
CodeHTTPMeaning
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.
POST /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]
  }'

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

Create Offer parameters
FieldTypeRequiredNotes
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

Create Offer: return codes
CodeHTTPMeaning
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.
POST /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"

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

View Offers parameters
FieldTypeRequiredNotes
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_id integer
The offer's id.
title, category, description, terms_and_conditions, url string
As created.
advertised_name string|null
The business name members see; null when your trading name is shown.
image string
Signed CloudFront URL of the image.
latitude, longitude, radius number
Where the offer is shown.
gender array
For example ["Male","Female"]; empty when the offer targets every gender.
interests array
{interest_id, interest_name} objects; empty when no interest targeting was applied.
start_datetime, expiry_datetime, creation_datetime string
ISO 8601, UTC.
offer_status string
Awaiting Payment, Active, Scheduled, Paused or Ended.
status string
enabled or disabled: whether an Admin has disabled the offer.

Response codes

View Offers: response codes
CodeHTTPMeaning
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.
GET /offers
curl -X GET "https://ads.agilis.dating/offers?environment=test&limit=20" \
  -H "x-api-key: YOUR_API_KEY_HERE"

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

Pause Offer parameters
FieldTypeRequiredNotes
offer_id path integer Yes Path parameter. Example: 45

Return codes

Pause Offer: return codes
CodeHTTPMeaning
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.
POST /offers/{offer_id}/pause
curl -X POST "https://ads.agilis.dating/offers/45/pause" \
  -H "x-api-key: YOUR_API_KEY_HERE"

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

Resume Offer parameters
FieldTypeRequiredNotes
offer_id path integer Yes Path parameter. Example: 45

Return codes

Resume Offer: return codes
CodeHTTPMeaning
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.
POST /offers/{offer_id}/resume
curl -X POST "https://ads.agilis.dating/offers/45/resume" \
  -H "x-api-key: YOUR_API_KEY_HERE"

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

View Offer Analytics parameters
FieldTypeRequiredNotes
offer_id path integer Yes Path parameter. Example: 45
interval query string Yes One of 24h, 7d, 30d, 60d, 90d or all.

Response codes

View Offer Analytics: response codes
CodeHTTPMeaning
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.
GET /offers/{offer_id}/analytics
curl -X GET "https://ads.agilis.dating/offers/45/analytics?interval=30d" \
  -H "x-api-key: YOUR_API_KEY_HERE"

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

Delete Offer parameters
FieldTypeRequiredNotes
offer_id path integer Yes Path parameter. Example: 45

Return codes

Delete Offer: return codes
CodeHTTPMeaning
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.
DELETE /offers/{offer_id}
curl -X DELETE "https://ads.agilis.dating/offers/45" \
  -H "x-api-key: YOUR_API_KEY_HERE"

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.

Return codes, and what each means on each endpoint
CodeEndpointHTTPMeaning 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.

Download

qr-code
appstore playstore
mobile-app-img

Socials

facebook instagram linkedin twitter tiktok
-->

We use cookies to enhance your browsing experience, serve personalized ads or content, and analyze our traffic. (View Cookie Policy)