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
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

2 Estimate Price Recommended

Calculate how many Ad Units the offer will cost before committing. This is especially important if you intend to pay from balance (payment method B) so you can confirm there are sufficient credits.

Endpoint: POST /offers/price-estimate

Price formula: Duration in hours × number of Agilis users within the target radius. The result is a float rounded to 2 decimal places.

curl -X POST "https://ads.agilis.dating/offers/price-estimate" \
  -H "x-api-key: YOUR_API_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "start_datetime": "2026-08-01T09:00:00Z",
    "end_datetime":   "2026-08-31T23:59:00Z",
    "location": [51.5074, -0.1278],
    "radius": 10
  }'
Parameters
FieldTypeRequiredNotes
start_datetimestringYesISO 8601, UTC only (e.g. 2026-08-01T09:00:00Z)
end_datetimestringYesISO 8601, UTC only
locationarrayYes[latitude, longitude]
radiusintegerYesMiles — minimum 5, maximum 30
Successful response — HTTP 200, S1
{
  "return_code": "S1",
  "price_estimate": 45.50
}
Error responses
HTTPBodyMeaning
400{"return_code": "E1", ...}One or more inputs are missing or invalid
400{"return_code": "E2", ...}start_datetime or end_datetime is not in valid ISO 8601 format
401{"message": "Unauthorised"}Authentication failure

3 Create the Offer

Submit the offer as a multipart/form-data request. The image must be sent as a binary file stream. The API runs automated content moderation on the image before the offer is created. If moderation fails, the offer is not created and no charge is made.

Endpoint: POST /offers

Parameters
FieldTypeRequiredNotes
titlestringYes
descriptionstringYes
terms_and_conditionsstringYesMaximum 500 characters
category_idintegerYesFrom Step 1
urlstringYesAdvertiser's offer URL
imagefile (binary)YesJPG, PNG or GIF; maximum 10 MB; sent as binary stream
currencystringYesGBP, USD, CAD or AUD (case-insensitive)
location[]floatYesTwo values: latitude then longitude
radiusintegerYesMiles — minimum 5
start_datetimestringYesISO 8601, UTC (e.g. 2026-08-01T09:00:00Z)
expiry_datetimestringYesISO 8601, UTC; minimum 60 seconds after start_datetime
payment_methodstringYesE = Stripe link by email; B = deduct from organisation balance
environmentstringYesLIVE or TEST (case-insensitive)
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 "currency=GBP" \
  -F "location[]=51.5074" \
  -F "location[]=-0.1278" \
  -F "radius=10" \
  -F "start_datetime=2026-08-01T09:00:00Z" \
  -F "expiry_datetime=2026-08-31T23:59:00Z" \
  -F "payment_method=E" \
  -F "environment=LIVE" \
  -F "image=@/path/to/offer-image.jpg"
Response immediately after creation — HTTP 200, S1

The offer is created but not yet active. Store the offer_id and present the payment_url to the user.

{
  "return_code": "S1",
  "offer_id": 45,
  "status": "Awaiting Payment",
  "payment_url": "https://checkout.stripe.com/c/pay/cs_live_..."
}

Direct your user to this Stripe Checkout page. The quantity on the Stripe invoice equals the number of Ad Units calculated in Step 2. Your user enters their card details and completes payment on Stripe's hosted page.

After payment, Stripe sends a checkout.session.completed webhook to Agilis, which activates the offer automatically. The status becomes Active if start_datetime is now or in the past, or Scheduled if it is in the future.

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 the organisation's Ad Unit balance is at least equal to the price estimate returned in Step 2. If the balance is insufficient, the API returns E3 and no offer is created. Payment is deducted immediately when the offer is created; no Stripe link is involved.

curl -X POST "https://ads.agilis.dating/offers" \
  -H "x-api-key: YOUR_API_KEY_HERE" \
  -H "Idempotency-Key: 3f1a9c7e-1b2d-4f9a-8c0e-2a6b5d4e7f10" \
  -F "title=Summer Sale — 20% Off" \
  -F "description=Discount valid through August on all summer items." \
  -F "terms_and_conditions=Valid for online purchases only. One per customer." \
  -F "category_id=1" \
  -F "url=https://www.example.com/summer-sale" \
  -F "currency=GBP" \
  -F "location[]=51.5074" \
  -F "location[]=-0.1278" \
  -F "radius=10" \
  -F "start_datetime=2026-08-01T09:00:00Z" \
  -F "expiry_datetime=2026-08-31T23:59:00Z" \
  -F "payment_method=B" \
  -F "environment=LIVE" \
  -F "image=@/path/to/offer-image.jpg"
Response when start date is in the future — HTTP 200, S1
{
  "return_code": "S1",
  "offer_id": 46,
  "status": "Scheduled",
  "payment_url": null
}
Response when start date has already passed — HTTP 200, S1
{
  "return_code": "S1",
  "offer_id": 47,
  "status": "Active",
  "payment_url": null
}

Ad Units are deducted from the organisation balance and Org Admins are notified by email in both cases.

Create Offer error responses (both payment paths)
HTTPBodyMeaning
400E1One or more inputs are missing or invalid (unsupported currency, invalid datetime, duration under 60 seconds, radius under 5 miles, oversized image, or wrong MIME type)
200E2Image failed automated content moderation — offer not created; choose a different image
200E3Organisation's Ad Unit balance is insufficient — top up and retry (payment_method=B only)
400E1Idempotency-Key request header was missing (message: "Idempotency-Key header is required.")
409Idempotency-Key reused while the first request is still being processed — retry shortly
422Idempotency-Key reused with a different payload
401UnauthorisedAuthentication failure
500Internal server errorUnexpected 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).

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 automatically disables its Stripe payment link first. Full request and response details for each are documented in the Pause and Resume, Analytics, and Delete Offer sections below.

Authentication Notes

  • API keys are created in the Agilis Ad Portal under Manage API Keys (Developer role).
  • Keys can be scoped to TEST or LIVE environments using the portal's test/live mode toggle.
  • A disabled key or disabled user account returns HTTP 401 {"message": "Unauthorised"} on any endpoint.
  • Keys have an optional expiry date; expired keys also return 401.

Environment Testing

Set environment: TEST in the Create Offer request to create offers in a sandbox. Test offers are only returned when calling GET /offers?environment=test, do not appear to real Agilis users in the app, and follow the same creation, payment, and management flow as live offers. Switch between environments using the test/live mode toggle in the Ad Portal.

Category List

The Category List API returns all active categories available to the organisation that owns the API key. It is typically called before creating an offer, because offer creation requires a valid category ID returned by this endpoint.

Header: x-api-key: your_api_key_here
Method: GET

category_id integer

Unique ID of the category.
Example: 1

category_name string

Name of the category.
Example: "Retail"
Response codes
200 — Categories retrieved successfully.
401 — Invalid API key or disabled account.
500 — Internal server error.
GET /categories
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())
Response (Python)

Create Offer

Creates a new offer for the authenticated advertiser. The request is sent as multipart/form-data with the offer image supplied as a binary file stream. The image is checked by automated content moderation before the offer is created.

Header: x-api-key: your_api_key_here
Header: Idempotency-Key: <unique-uuid> required
Content-Type: multipart/form-data

category_id integer, required

A valid category ID from the Category List API.
Example: 1

title string, required

Example: "20% off summer sale"

description string, required

Example: "Discount valid through July"

terms_and_conditions string, required

Maximum 500 characters.
Example: "Valid online only."

url string, required

Example: "https://advertiseroffer.com"

image binary, required

JPG, PNG or GIF, maximum 10MB, sent as a binary file stream.
Recommended size: 1080x1080px

currency string, required

One of GBP, USD, CAD, AUD (case-insensitive).

location[] array, required

Latitude and longitude as two values.
Example: [51.5074, -0.1278]

radius integer, required

Radius in miles, minimum 5.
Example: 10

start_datetime string, required

ISO 8601, UTC.
Example: "2026-07-10T08:00:00Z"

expiry_datetime string, required

ISO 8601, UTC.
Example: "2026-07-12T20:30:00Z"

payment_method string, required

E = Stripe payment link by email; B = deduct from organisation balance.

environment string, required

One of TEST or LIVE (case-insensitive).
Return codes
S1 — Offer created (payment link issued, or Live/Scheduled if paid from balance).
E1 — One or more inputs are missing or invalid (including unsupported currency).
E2 — The uploaded image was rejected by content moderation.
E3 — Insufficient Ad Unit balance.
409 — Idempotency-Key reused while the first request is still processing.
422 — Idempotency-Key reused with a different payload.
POST /offers
import requests

url = "https://ads.agilis.dating/offers"
headers = {
    "x-api-key": "your_api_key_here",
    "Idempotency-Key": "3f1a9c7e-1b2d-4f9a-8c0e-2a6b5d4e7f10"
}

files = {
    "image": ("offer.jpg", open("/path/to/offer.jpg", "rb"), "image/jpeg")
}
data = {
    "category_id": 1,
    "title": "20% off summer sale",
    "description": "Discount valid through July",
    "terms_and_conditions": "Valid online only.",
    "url": "https://advertiseroffer.com",
    "currency": "GBP",
    "location[]": [51.5074, -0.1278],
    "radius": 10,
    "start_datetime": "2026-07-10T08:00:00Z",
    "expiry_datetime": "2026-07-12T20:30:00Z",
    "payment_method": "E",
    "environment": "LIVE"
}

response = requests.post(url, headers=headers, files=files, data=data)
print(response.json())
Response (Python)
{
  "return_code": "S1",
  "offer_id": 45,
  "status": "Awaiting Payment",
  "payment_url": "https://checkout.stripe.com/c/pay/cs_test_..."
}

View Offers

Returns a paginated list of offers created by the authenticated advertiser for the requested environment, newest first. Offers whose image was rejected by content moderation are never created, so they are not returned, and deleted offers are excluded. Page through results with limit and cursor.

Header: x-api-key: your_api_key_here
Method: GET

environment string, required

One of test or live (case-insensitive).
Example: /offers?environment=test

limit integer, optional

Page size. Default 20, maximum 100.

cursor string, optional

Opaque token from the previous page's next_cursor. Omit for the first page.

offer_status string, optional

Lifecycle filter: awaiting_payment, active, paused or ended (case-insensitive).

created_after string, optional

ISO 8601, UTC. Offers created at or after this time.

created_before string, optional

ISO 8601, UTC. Offers created at or before this time.

Each offer object contains: offer_id, title, category, description, terms_and_conditions, url, image (signed CloudFront URL), latitude, longitude, radius, gender (array such as ["Male","Female"], or null), start_datetime, expiry_datetime, creation_datetime, offer_status (Active, Paused, Ended or Awaiting Payment) and status (enabled or disabled — whether an Admin has disabled the offer).

Response codes
200 — Page returned (empty data array if none match).
400 — Invalid query parameter (limit, cursor, offer_status or date).
401 — Invalid API key or disabled account.
500 — Internal server error.
GET /offers?environment=test
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())
Response (Python)
{
  "data": [
    {
      "offer_id": 45,
      "title": "20% off summer sale",
      "category": "Retail",
      "description": "Discount valid through July",
      "terms_and_conditions": "Valid online only.",
      "url": "https://advertiseroffer.com",
      "image": "https://ads.agilis.dating/92/offers/45/offer_orig.png",
      "latitude": 51.5074,
      "longitude": -0.1278,
      "radius": 10,
      "gender": ["Male", "Female"],
      "start_datetime": "2026-07-10T08:00:00Z",
      "expiry_datetime": "2026-07-12T20:30:00Z",
      "creation_datetime": "2026-06-01T10:00:00Z",
      "offer_status": "Active",
      "status": "enabled"
    }
  ],
  "pagination": {
    "limit": 20,
    "count": 1,
    "has_more": false,
    "next_cursor": null
  }
}

Pause and Resume Offer

Pauses a running offer or resumes a paused offer. These are two separate endpoints. The offer must belong to the account that owns the API key.

Header: x-api-key: your_api_key_here
Method: POST

offer_id integer, required

Path parameter.
Example: 45
Return codes
S1 — Offer paused successfully.
S2 — Offer resumed successfully.
E2 — Offer has already ended.
E3 — Offer is already paused (pause request).
E4 — Offer is already running (resume request).
E5 — Offer is awaiting payment.
POST /offers/{offer_id}/pause
import requests

headers = {"x-api-key": "your_api_key_here"}

# Pause an offer
pause = requests.post(
    "https://ads.agilis.dating/offers/45/pause", headers=headers)
print(pause.json())

# Resume an offer
resume = requests.post(
    "https://ads.agilis.dating/offers/45/resume", headers=headers)
print(resume.json())
Response (Python)
{
  "return_code": "S1",
  "message": "Offer has been paused."
}

Delete Offer

Deletes an offer belonging to the authenticated account. If the offer is awaiting payment, its Stripe payment link is disabled before deletion.

Header: x-api-key: your_api_key_here
Method: DELETE

offer_id integer, required

Path parameter.
Example: 45
Return codes
S1 — Offer deleted successfully.
E1 — Invalid input, or the offer does not belong to this account.
DELETE /offers/{offer_id}
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())
Response (Python)
{
  "return_code": "S1",
  "message": "Offer has been deleted."
}

View Offer Analytics

Returns view and click metrics for a specific offer over the requested time interval. The offer must belong to the account that owns the API key.

Header: x-api-key: your_api_key_here
Method: GET

offer_id integer, required

Path parameter.
Example: 45

interval string, required

One of 24h, 7d, 30d, 60d, 90d or all.
Response codes
200 — Analytics returned successfully.
401 — Invalid API key, disabled account, or offer not owned by this account.
500 — Internal server error.
GET /offers/{offer_id}/analytics
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())
Response (Python)
{
  "offer_id": 45,
  "interval": "30d",
  "start_datetime": "2026-07-10T08:00:00Z",
  "end_datetime": "2026-08-09T08:00:00Z",
  "status": "Active",
  "metrics": {
    "views": 4789,
    "clicks": 1032
  }
}

Offer Price Estimate

Calculates the price of an offer in Ad Units before it is created, based on the duration and the number of Agilis users within the target radius.

Header: x-api-key: your_api_key_here
Content-Type: application/json
Method: POST

start_datetime string, required

ISO 8601, UTC.
Example: "2026-07-10T08:00:00Z"

end_datetime string, required

ISO 8601, UTC.
Example: "2026-07-12T20:30:00Z"

location array, required

Latitude and longitude.
Example: [51.5074, -0.1278]

radius integer, required

Miles, minimum 5, maximum 30.
Example: 10
Return codes
S1 — Estimate calculated successfully.
E1 — One or more inputs are missing or invalid.
E2 — A date/time is not in valid ISO 8601 format.
POST /offers/price-estimate
import requests

url = "https://ads.agilis.dating/offers/price-estimate"
headers = {"x-api-key": "your_api_key_here"}
payload = {
    "start_datetime": "2026-07-10T08:00:00Z",
    "end_datetime": "2026-07-12T20:30:00Z",
    "location": [51.5074, -0.1278],
    "radius": 10
}

response = requests.post(url, headers=headers, json=payload)
print(response.json())
Response (Python)
{
  "return_code": "S1",
  "price_estimate": 45.50
}

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)