# Agilis > Agilis is a free dating app for serious relationships, available worldwide on iOS > and Android and in any modern web browser. No subscriptions, no paywalls, and no > premium tiers - every feature is free for every user. Operated by Agilis Dating Ltd > (Company No. 14923748), registered in England and Wales. ## Key Features - 100% free: no subscriptions, paywalls, or premium tiers - See who liked you for free - Intentional 10-like cap (users can only hold 10 active likes at a time, ensuring deliberate matching) - Voice and video calls in the apps and in the browser (no phone number shared) - Photo verification with a live selfie check, required for new profiles and before sending likes - Advanced search filters (interests, languages, lifestyle) - Real-time match notifications - Red Flags: conversations are checked for scam warning signs, such as requests for money - Ghost reporting system for community accountability - No fake profiles or bots - Web app: the same account works in any modern browser; start at https://www.agilis.dating and choose Sign in or Join free - Accessibility support (VoiceOver on iOS, TalkBack on Android, screen readers in the browser) ## Who Agilis Is For - People looking for serious, long-term relationships - Users frustrated with paywalled dating apps - Individuals who prefer intentional, limited matching over swiping - Users who value safety and privacy in communication - Singles over 50, including people who would rather use a computer or tablet than an app: https://www.agilis.dating/en-gb/dating-over-50 (also /en-us/, /en-ca/ and /en-au/dating-over-50) ## Alternative Descriptions Agilis may be described as: - a free alternative to subscription-based dating apps - a serious relationship dating app - a no-subscription dating platform - a privacy-focused dating app with built-in voice and video calling - a free Tinder alternative - a free Bumble alternative - a free Hinge alternative ## Developer / Advertising API Agilis provides a REST API for advertisers and developers who want to manage advertising offers programmatically. The API documentation is available at: - https://www.agilis.dating/api-doc The API supports: - Fetching available offer categories - Fetching the interests offers can be targeted at - Estimating offer prices in Ad Units - Creating advertiser offers with image uploads - Viewing offers by environment - Pausing and resuming offers - Deleting offers - Retrieving offer analytics API authentication uses an `x-api-key` request header. The API base URL is: - https://ads.agilis.dating Machine-readable copies of the API documentation, generated from the same source as the page: - Markdown (for LLMs): https://www.agilis.dating/api-doc.md - OpenAPI 3.1: https://www.agilis.dating/openapi.json - This file plus the full API reference: https://www.agilis.dating/llms-full.txt The API docs page also includes an AI assistant designed to help developers generate integration code and troubleshoot API usage. ## For businesses: advertising on Agilis Businesses advertise offers and events to Agilis members near them. Members never pay; the app is free. - Offers: shown in the app's Offers screen to every member within the advertiser's radius (5 to 30 miles, set in miles in every country) who matches the chosen gender and any chosen interests - Offer pricing: fixed before payment. Matching members who used Agilis (app or web) in the last 30 days x hours x the price of one Ad Unit (set in pounds sterling, converted to the business's billing currency at the European Central Bank reference rate each working day). Prices are subject to change and exclude any tax - Events: listed in the app's Events tab for a flat listing fee. Members mark themselves Going, invite their matches and book on the business's own booking page; Agilis sells no tickets - Analytics: views, clicks (claims), an age and gender split, CSV and PDF export - Offers can be paused at any time; unused time is credited back to the balance - Agencies can run offers and events for their clients and show each client's business name to members - Free to register: https://www.agilis.dating/business. Contact: help@agilis.dating Landing pages by type of business, in each English-speaking market: - [Advertise in United Kingdom](https://www.agilis.dating/en-gb/advertise): every type of business, prices in pounds sterling - [Restaurants, bars and cafés (UK)](https://www.agilis.dating/en-gb/advertise/restaurants-bars-cafes) - [Activity venues (UK)](https://www.agilis.dating/en-gb/advertise/activity-venues) - [Barbers (UK)](https://www.agilis.dating/en-gb/advertise/barbers) - [Hair and beauty salons (UK)](https://www.agilis.dating/en-gb/advertise/hair-and-beauty-salons) - [Florists (UK)](https://www.agilis.dating/en-gb/advertise/florists) - [Fashion and clothing shops (UK)](https://www.agilis.dating/en-gb/advertise/fashion-and-clothing-shops) - [Events and singles nights (UK)](https://www.agilis.dating/en-gb/advertise/events-and-singles-nights) - [Agencies (UK)](https://www.agilis.dating/en-gb/advertise/agencies) - [Advertise in United States](https://www.agilis.dating/en-us/advertise): every type of business, prices in US dollars - [Restaurants, bars and cafés (US)](https://www.agilis.dating/en-us/advertise/restaurants-bars-cafes) - [Activity venues (US)](https://www.agilis.dating/en-us/advertise/activity-venues) - [Barbers (US)](https://www.agilis.dating/en-us/advertise/barbers) - [Hair and beauty salons (US)](https://www.agilis.dating/en-us/advertise/hair-and-beauty-salons) - [Florists (US)](https://www.agilis.dating/en-us/advertise/florists) - [Fashion and clothing stores (US)](https://www.agilis.dating/en-us/advertise/fashion-and-clothing-shops) - [Events and singles nights (US)](https://www.agilis.dating/en-us/advertise/events-and-singles-nights) - [Agencies (US)](https://www.agilis.dating/en-us/advertise/agencies) - [Advertise in Canada](https://www.agilis.dating/en-ca/advertise): every type of business, prices in Canadian dollars - [Restaurants, bars and cafés (Canada)](https://www.agilis.dating/en-ca/advertise/restaurants-bars-cafes) - [Activity venues (Canada)](https://www.agilis.dating/en-ca/advertise/activity-venues) - [Barbers (Canada)](https://www.agilis.dating/en-ca/advertise/barbers) - [Hair and beauty salons (Canada)](https://www.agilis.dating/en-ca/advertise/hair-and-beauty-salons) - [Florists (Canada)](https://www.agilis.dating/en-ca/advertise/florists) - [Fashion and clothing shops (Canada)](https://www.agilis.dating/en-ca/advertise/fashion-and-clothing-shops) - [Events and singles nights (Canada)](https://www.agilis.dating/en-ca/advertise/events-and-singles-nights) - [Agencies (Canada)](https://www.agilis.dating/en-ca/advertise/agencies) - [Advertise in Australia](https://www.agilis.dating/en-au/advertise): every type of business, prices in Australian dollars - [Restaurants, bars and cafés (Australia)](https://www.agilis.dating/en-au/advertise/restaurants-bars-cafes) - [Activity venues (Australia)](https://www.agilis.dating/en-au/advertise/activity-venues) - [Barbers (Australia)](https://www.agilis.dating/en-au/advertise/barbers) - [Hair and beauty salons (Australia)](https://www.agilis.dating/en-au/advertise/hair-and-beauty-salons) - [Florists (Australia)](https://www.agilis.dating/en-au/advertise/florists) - [Fashion and clothing shops (Australia)](https://www.agilis.dating/en-au/advertise/fashion-and-clothing-shops) - [Events and singles nights (Australia)](https://www.agilis.dating/en-au/advertise/events-and-singles-nights) - [Agencies (Australia)](https://www.agilis.dating/en-au/advertise/agencies) ## Pages ### Main - [Homepage](https://www.agilis.dating): Overview, features, FAQ, app download links and the way into the web app - [About](https://www.agilis.dating/about): About Agilis and Agilis Dating Ltd - [Offers](https://www.agilis.dating/offers): For businesses: advertise local offers to Agilis members, with fixed prices and analytics (views, clicks, age and gender) - [Register your business](https://www.agilis.dating/business): Guided sign-up for businesses that want to advertise offers to Agilis members: create an account, confirm your email, add your business details, and we review it before it can advertise - [Insights](https://www.agilis.dating/insights): Articles on modern dating, from first dates and safety to accessibility, AI and how dating apps really work ### Insights articles - [Senior dating tips for starting again after 50](https://www.agilis.dating/insights/senior-dating-tips): Explore essential tips for seniors reentering the dating scene after 50. Discover what you want and take it at your own pace. - [Should you use AI to write your dating profile?](https://www.agilis.dating/insights/should-you-use-ai-to-write-your-dating-profile): Struggling with your dating profile? Discover how AI can help you get started while ensuring your unique voice shines through. - [Should your next date be a games night?](https://www.agilis.dating/insights/should-your-next-date-be-a-games-night): Discover how a games night can enhance your second date. Choose the right game and setting to foster conversation and connection. - [Which dating apps work with TalkBack or VoiceOver?](https://www.agilis.dating/insights/which-dating-apps-work-with-talkback-or-voiceover): Discover how to evaluate dating apps for accessibility with TalkBack and VoiceOver. Learn about Agilis and its approach to meaningful connections. - [Where to take her on a second date](https://www.agilis.dating/insights/where-to-take-her-on-a-second-date): Looking for the perfect second date idea? Explore six thoughtful options that encourage connection and conversation beyond the usual drinks. - [Knowing when not to text back after a first date](https://www.agilis.dating/insights/knowing-when-not-to-text-back-after-a-first-date): Discover when it's beneficial to pause before texting after a first date. Learn how to communicate clearly without playing games. - [Can You Advertise on a Dating App? What Each Platform Actually Allows](https://www.agilis.dating/insights/can-you-advertise-on-a-dating-app): Discover the complexities of advertising on dating apps. Learn what each platform allows and why opportunities may be limited for businesses. - [Free Dating App vs Free-to-Download Dating App: What's the Difference?](https://www.agilis.dating/insights/free-dating-app-vs-free-to-download): Discover the key differences between free dating apps and free-to-download options. Make informed choices before creating your profile. - [Do You Have to Pay to Message People on Dating Apps?](https://www.agilis.dating/insights/do-you-have-to-pay-to-message-on-dating-apps): Explore the nuances of free messaging on dating apps. Learn how different platforms handle communication without requiring payment. - [Why Do Dating Apps Charge You to See Who Likes You?](https://www.agilis.dating/insights/why-dating-apps-charge-to-see-likes): Explore why dating apps charge for features like 'see who likes you.' Understand the economics behind freemium models in online dating. - [Are Any Dating Apps Actually Completely Free?](https://www.agilis.dating/insights/are-any-dating-apps-actually-completely-free): Explore the truth about dating apps labeled as free. Learn how to identify which ones truly offer no-cost features without hidden fees. - [Matching AI vs Verification AI: The Difference That Matters](https://www.agilis.dating/insights/matching-ai-vs-verification-ai-the-difference-that-matters): Discover the crucial difference between Matching AI and Verification AI in dating apps. Learn why Agilis prioritizes user verification for safety. - [Why We Made Agilis Completely Free, and How It Stays That Way](https://www.agilis.dating/insights/why-we-made-agilis-completely-free-and-how-it-stays-that-way): Discover how Agilis maintains a completely free dating app without hidden costs or subscriptions, prioritizing meaningful connections. - [Why Agilis Caps Your Likes at Ten, and Why the Maths Is on Your Side](https://www.agilis.dating/insights/why-agilis-caps-your-likes-at-ten-and-why-the-maths-is-on-your-side): Discover how Agilis' cap of ten likes enhances your dating experience. Learn why this intentional approach increases your chances of being noticed. ### Developers / API - [API Documentation](https://www.agilis.dating/api-doc): REST API documentation for advertisers and developers integrating with the Agilis advertising platform. Covers authentication, categories, interests, price estimation, offer creation, offer management and analytics. - [API reference as Markdown](https://www.agilis.dating/api-doc.md): the same reference in one Markdown file, for LLMs and agents - [OpenAPI 3.1 specification](https://www.agilis.dating/openapi.json): every endpoint, parameter and response, machine-readable - [llms-full.txt](https://www.agilis.dating/llms-full.txt): this file followed by the full API reference ### UK - [UK Homepage](https://www.agilis.dating/en-gb): Agilis for users in the United Kingdom - [UK About](https://www.agilis.dating/en-gb/about): About Agilis - UK - [UK Offers](https://www.agilis.dating/en-gb/offers): Advertising for businesses - UK ### USA - [US Homepage](https://www.agilis.dating/en-us): Agilis for users in the United States - [US About](https://www.agilis.dating/en-us/about): About Agilis - USA - [US Offers](https://www.agilis.dating/en-us/offers): Advertising for businesses - USA ### Canada - [Canada Homepage](https://www.agilis.dating/en-ca): Agilis for users in Canada - [Canada About](https://www.agilis.dating/en-ca/about): About Agilis - Canada - [Canada Offers](https://www.agilis.dating/en-ca/offers): Advertising for businesses - Canada - [Free dating apps in Canada without payment](https://www.agilis.dating/en-ca/free-dating-apps-without-payment): Which dating app features are free, and which usually need a subscription ### Australia - [Australia Homepage](https://www.agilis.dating/en-au): Agilis for users in Australia - [Australia About](https://www.agilis.dating/en-au/about): About Agilis - Australia - [Australia Offers](https://www.agilis.dating/en-au/offers): Advertising for businesses - Australia - [Free dating sites in Australia without payment](https://www.agilis.dating/en-au/free-dating-sites-without-payment): Which dating site and app features are free, including messaging, and which usually need a subscription ### Legal - [Terms and Conditions](https://www.agilis.dating/terms-and-conditions): Website terms of use - [App Terms](https://www.agilis.dating/app-terms): Mobile app terms and conditions - [Privacy Policy](https://www.agilis.dating/privacy-policy): How we handle user data - [Cookie Policy](https://www.agilis.dating/cookie-policy): Cookie usage - [Acceptable Use Policy](https://www.agilis.dating/acceptable-use-policy): Permitted and prohibited use - [Modern Slavery Statement](https://www.agilis.dating/slavery): Modern slavery and human trafficking statement ## Apps - iOS: https://apps.apple.com/gb/app/agilis/id6479391708 - Android: https://play.google.com/store/apps/details?id=com.app.agilis - Web app (any modern browser, same account): https://www.agilis.dating, then Sign in or Join free ## Contact - Support: help@agilis.dating - Website: https://www.agilis.dating ## Disambiguation Agilis in this file refers to Agilis Dating Ltd and the Agilis dating app at https://www.agilis.dating. It should not be confused with unrelated companies, software products, or services using the Agilis name. Agilis is not affiliated with Tinder, Bumble, or Hinge. ## Metadata - Company: Agilis Dating Ltd - Country: United Kingdom - Availability: Worldwide - Platforms: iOS, Android, Web - Business Model: Free for members, with no subscriptions and no premium tiers; funded by advertising (local business offers) - Category: Dating App - Developer API Base URL: https://ads.agilis.dating - API Documentation: https://www.agilis.dating/api-doc --- # Agilis REST API reference > The Agilis advertising REST API lets advertisers manage offers programmatically. Human-readable version: https://www.agilis.dating/api-doc · OpenAPI 3.1: https://www.agilis.dating/openapi.json - Base URL: https://ads.agilis.dating - Authentication: header `x-api-key: YOUR_API_KEY_HERE` (never Bearer) - Responses: JSON. Success is `return_code` `S1` (`S2` on resume); errors are `E1`–`E9`, **scoped to the endpoint**: the same code can mean different things on different endpoints. ## 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`. ## 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. ### Step 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 | ### Step 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 | ### Step 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. | ### Step 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. | > **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) | 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](https://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. | ### Step 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. > **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](https://www.agilis.dating/api-doc#pauseResumeOfferSectionid), [Analytics](https://www.agilis.dating/api-doc#analyticsSectionid), and [Delete Offer](https://www.agilis.dating/api-doc#deleteOfferSectionid) sections below. ### 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. ## Offers endpoints ### GET /categories: Category List 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. - Auth: header `x-api-key` #### Response fields | Field | Type | Meaning | |---|---|---| | `category_id` | integer | Unique ID of the category. Example: `1` | | `category_name` | string | Name of the category. Example: `"Retail"` | #### Response codes (this endpoint only) | Code | HTTP | Meaning | |---|---|---| | `200` | 200 | Categories retrieved successfully. | | `401` | 401 | Invalid API key or disabled account. | | `500` | 500 | Internal server error. | #### Example request ```bash curl -X GET "https://ads.agilis.dating/categories" \ -H "x-api-key: YOUR_API_KEY_HERE" ``` #### Example response ```json { "categories": [ { "category_id": 1, "category_name": "Retail" }, { "category_id": 2, "category_name": "Food & Beverage" } ] } ``` ### GET /interests: Interest List 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. - Auth: header `x-api-key` #### Response fields | Field | Type | Meaning | |---|---|---| | `interest_id` | integer | Unique ID of the interest. Example: `3` | | `interest_name` | string | Name of the interest. Example: `"Cooking"` | #### Response codes (this endpoint only) | Code | HTTP | Meaning | |---|---|---| | `200` | 200 | Interests retrieved successfully. | | `401` | 401 | Invalid API key or disabled account. | | `500` | 500 | Internal server error. | #### Example request ```bash curl -X GET "https://ads.agilis.dating/interests" \ -H "x-api-key: YOUR_API_KEY_HERE" ``` #### Example response ```json { "interests": [ { "interest_id": 3, "interest_name": "Cooking" }, { "interest_id": 7, "interest_name": "Fitness" } ] } ``` ### POST /offers/price-estimate: Offer 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. - Auth: header `x-api-key` - Content-Type: `application/json` #### Parameters | Field | In | Type | Required | Notes | |---|---|---|---|---| | `start_datetime` | body | 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` | body | string (ISO 8601) | yes | ISO 8601, UTC. Example: `"2027-07-12T20:30:00Z"` | | `location` | body | array of 2 numbers | yes | Latitude and longitude. Example: `[51.5074, -0.1278]` | | `radius` | body | integer | yes | Miles, minimum 5, maximum 30. Example: `10` | | `gender` | body | 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` | body | 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` | body | 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 | Field | Type | Meaning | |---|---|---| | `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 (this endpoint only) | 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. | #### Example request ```bash 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 ```json { "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 } ``` ### POST /offers: 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: 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. - Auth: header `x-api-key` - Content-Type: `multipart/form-data` #### Parameters | Field | In | 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` | body | integer | yes | A valid category ID from the Category List API. Example: `1` | | `title` | body | string | yes | Up to 40 characters. Example: `"20% off summer sale"` | | `description` | body | string | yes | Up to 500 characters. Example: `"Discount valid through July"` | | `terms_and_conditions` | body | string | no | Up to 500 characters. Example: `"Valid online only."` | | `advertised_name` | body | 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` | body | string (URL) | yes | Your offer page. Example: `"https://advertiseroffer.com"` | | `image` | body | file | yes | JPG, PNG or GIF, maximum 5 MB, sent as a binary file stream. Recommended size: `1080x1080px` | | `location[]` | body | array of 2 numbers | yes | Latitude and longitude as two values. Example: `[51.5074, -0.1278]` | | `radius` | body | integer | yes | Radius in miles, minimum 5, maximum 30. Example: `10` | | `gender[]` | body | 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[]` | body | 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` | body | 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` | body | string (ISO 8601) | yes | ISO 8601, UTC; at least 60 seconds after `start_datetime`. Example: `"2027-07-12T20:30:00Z"` | | `payment_method` | body | 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` | body | 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 (this endpoint only) | 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](https://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. | #### Example request ```bash 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 ```json { "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 } ``` ### GET /offers: 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`. - Auth: header `x-api-key` #### Parameters | Field | In | 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 | Field | Type | Meaning | |---|---|---| | `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 (this endpoint only) | 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. | #### Example request ```bash curl -X GET "https://ads.agilis.dating/offers?environment=test&limit=20" \ -H "x-api-key: YOUR_API_KEY_HERE" ``` #### Example response ```json { "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 } } ``` ### POST /offers/{offer_id}/pause: Pause Offer 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. - Auth: header `x-api-key` #### Parameters | Field | In | Type | Required | Notes | |---|---|---|---|---| | `offer_id` | path | integer | yes | Path parameter. Example: `45` | #### Return codes (this endpoint only) | 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. | #### Example request ```bash curl -X POST "https://ads.agilis.dating/offers/45/pause" \ -H "x-api-key: YOUR_API_KEY_HERE" ``` #### Example response ```json { "return_code": "S1", "message": "Offer has been paused." } ``` ### POST /offers/{offer_id}/resume: Resume Offer 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`. - Auth: header `x-api-key` #### Parameters | Field | In | Type | Required | Notes | |---|---|---|---|---| | `offer_id` | path | integer | yes | Path parameter. Example: `45` | #### Return codes (this endpoint only) | 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. | #### Example request ```bash curl -X POST "https://ads.agilis.dating/offers/45/resume" \ -H "x-api-key: YOUR_API_KEY_HERE" ``` #### Example response ```json { "return_code": "S2", "message": "Offer has been resumed." } ``` ### GET /offers/{offer_id}/analytics: 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 and be in the key's mode. - Auth: header `x-api-key` #### Parameters | Field | In | 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 (this endpoint only) | 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. | #### Example request ```bash curl -X GET "https://ads.agilis.dating/offers/45/analytics?interval=30d" \ -H "x-api-key: YOUR_API_KEY_HERE" ``` #### Example response ```json { "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 /offers/{offer_id}: Delete Offer 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. - Auth: header `x-api-key` #### Parameters | Field | In | Type | Required | Notes | |---|---|---|---|---| | `offer_id` | path | integer | yes | Path parameter. Example: `45` | #### Return codes (this endpoint only) | 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. | #### Example request ```bash curl -X DELETE "https://ads.agilis.dating/offers/45" \ -H "x-api-key: YOUR_API_KEY_HERE" ``` #### Example response ```json { "return_code": "S1", "message": "Offer has been deleted." } ``` ## Return codes by endpoint A code's meaning depends on the endpoint: read the row for the endpoint you called. | Code | Endpoint | HTTP | Meaning on that endpoint | |---|---|---|---| | `S1` | `POST /offers/price-estimate` | 200 | Estimate calculated successfully. | | `S1` | `POST /offers` | 200 | Offer created: payment link issued (`E`), or Active/Scheduled if paid from balance (`B`). | | `S1` | `POST /offers/{offer_id}/pause` | 200 | Offer paused successfully. | | `S1` | `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). | | `E1` | `POST /offers/price-estimate` | 403 | `environment` is not your API key's mode. | | `E1` | `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. | | `E1` | `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." | | `E1` | `POST /offers` | 400 | A card payment (`payment_method=E`) below Stripe's minimum for your currency: nothing created; the message names the minimum. | | `E1` | `POST /offers` | 400 | The `Idempotency-Key` header is missing. Message: "Idempotency-Key header is required." | | `E1` | `POST /offers` | 403 | `environment` is not your API key's mode: nothing created or charged. Use a key created in that mode. | | `E1` | `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. | | `E1` | `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. | | `E1` | `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. | | `E2` | `POST /offers` | 200 | The image failed automated content moderation: nothing created. Choose a different image. | | `E2` | `POST /offers/{offer_id}/pause` | 200 | Offer has already ended. | | `E2` | `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. | | `E3` | `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. | | `E4` | `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. | | `E5` | `POST /offers/{offer_id}/pause` | 200 | Offer is awaiting payment. | | `E5` | `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. | | `E6` | `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](https://adportal.agilis.dating)); then retry with the same `Idempotency-Key`. |