{
    "openapi": "3.1.0",
    "info": {
        "title": "Agilis Advertising REST API",
        "version": "1.0.0",
        "summary": "Create and manage offers for Agilis members, estimate prices and read analytics.",
        "description": "Return codes are in the JSON body (`return_code`). They are scoped to the endpoint: the same code can mean different things on different endpoints, so read each operation's `x-return-codes`.\n\nRate limit: 60 requests a minute per client IP address. Responses carry `X-RateLimit-Limit` and `X-RateLimit-Remaining`; over the limit the API returns 429 with `Retry-After`.\n\nHuman-readable reference: https://www.agilis.dating/api-doc · Markdown: https://www.agilis.dating/api-doc.md",
        "contact": {
            "name": "Agilis support",
            "email": "help@agilis.dating",
            "url": "https://www.agilis.dating/api-doc"
        },
        "termsOfService": "https://www.agilis.dating/terms-and-conditions"
    },
    "servers": [
        {
            "url": "https://ads.agilis.dating",
            "description": "Production. TEST or LIVE is chosen by the API key (pk_test_ / pk_live_) and the environment parameter."
        }
    ],
    "security": [
        {
            "apiKey": []
        }
    ],
    "tags": [
        {
            "name": "Offers"
        }
    ],
    "paths": {
        "/categories": {
            "get": {
                "operationId": "listCategories",
                "summary": "Category List",
                "description": "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.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#categoryListSection"
                },
                "responses": {
                    "200": {
                        "description": "Categories retrieved successfully."
                    },
                    "401": {
                        "description": "Invalid API key or disabled account."
                    },
                    "500": {
                        "description": "Internal server error."
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/interests": {
            "get": {
                "operationId": "listInterests",
                "summary": "Interest List",
                "description": "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.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#interestListSection"
                },
                "responses": {
                    "200": {
                        "description": "Interests retrieved successfully."
                    },
                    "401": {
                        "description": "Invalid API key or disabled account."
                    },
                    "500": {
                        "description": "Internal server error."
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/offers/price-estimate": {
            "post": {
                "operationId": "estimateOfferPrice",
                "summary": "Offer Price Estimate",
                "description": "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.\n\n`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.\n\nThere is no `currency` parameter: the estimate is always in your organisation's billing currency, named in the response's `currency` field.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#priceEstimateSectionid"
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "start_datetime": {
                                        "type": "string",
                                        "format": "date-time",
                                        "description": "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": {
                                        "type": "string",
                                        "format": "date-time",
                                        "description": "ISO 8601, UTC. Example: `\"2027-07-12T20:30:00Z\"`"
                                    },
                                    "location": {
                                        "type": "array",
                                        "items": {
                                            "type": "number"
                                        },
                                        "maxItems": 2,
                                        "minItems": 2,
                                        "description": "Latitude and longitude. Example: `[51.5074, -0.1278]`"
                                    },
                                    "radius": {
                                        "type": "integer",
                                        "minimum": 5,
                                        "maximum": 30,
                                        "description": "Miles, minimum 5, maximum 30. Example: `10`"
                                    },
                                    "gender": {
                                        "type": "array",
                                        "items": {
                                            "type": "string",
                                            "enum": [
                                                "Male",
                                                "Female",
                                                "Non-binary",
                                                "All"
                                            ]
                                        },
                                        "description": "One or more of `Male`, `Female`, `Non-binary`, `All`. Omit, or include `All`, to reach every gender. Example: `[\"Male\", \"Female\"]`"
                                    },
                                    "interests": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer"
                                        },
                                        "description": "Interest ids from `GET /interests`. A user matches if they share any of them. Omit to apply no interest filter. Example: `[3, 7]`"
                                    },
                                    "environment": {
                                        "type": "string",
                                        "enum": [
                                            "TEST",
                                            "LIVE",
                                            "test",
                                            "live"
                                        ],
                                        "description": "`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."
                                    }
                                },
                                "required": [
                                    "start_datetime",
                                    "end_datetime",
                                    "location",
                                    "radius"
                                ]
                            },
                            "example": {
                                "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
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "S1: Estimate calculated successfully."
                    },
                    "400": {
                        "description": "E1: One or more inputs are missing or invalid, or `start_datetime` is in the past (more than 60 seconds before now).\n\nE2: A date/time is not in valid ISO 8601 format.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Authentication failure."
                    },
                    "403": {
                        "description": "E1: `environment` is not your API key's mode.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                },
                "x-return-codes": {
                    "S1": "Estimate calculated successfully.",
                    "E1": "One or more inputs are missing or invalid, or `start_datetime` is in the past (more than 60 seconds before now). / `environment` is not your API key's mode.",
                    "E2": "A date/time is not in valid ISO 8601 format."
                }
            }
        },
        "/offers": {
            "post": {
                "operationId": "createOffer",
                "summary": "Create Offer",
                "description": "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.\n\n**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`.\n\n**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`.\n\nThere is no `currency` parameter: every offer is priced and charged in your organisation's billing currency, named in the response.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#createOfferSectionid"
                },
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": true,
                        "description": "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`.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "category_id": {
                                        "type": "integer",
                                        "description": "A valid category ID from the Category List API. Example: `1`"
                                    },
                                    "title": {
                                        "type": "string",
                                        "maxLength": 40,
                                        "description": "Up to 40 characters. Example: `\"20% off summer sale\"`"
                                    },
                                    "description": {
                                        "type": "string",
                                        "maxLength": 500,
                                        "description": "Up to 500 characters. Example: `\"Discount valid through July\"`"
                                    },
                                    "terms_and_conditions": {
                                        "type": "string",
                                        "maxLength": 500,
                                        "description": "Up to 500 characters. Example: `\"Valid online only.\"`"
                                    },
                                    "advertised_name": {
                                        "type": "string",
                                        "maxLength": 100,
                                        "description": "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": {
                                        "type": "string",
                                        "format": "uri",
                                        "description": "Your offer page. Example: `\"https://advertiseroffer.com\"`"
                                    },
                                    "image": {
                                        "type": "string",
                                        "contentMediaType": "application/octet-stream",
                                        "description": "JPG, PNG or GIF, maximum 5 MB, sent as a binary file stream. Recommended size: `1080x1080px`"
                                    },
                                    "location": {
                                        "type": "array",
                                        "items": {
                                            "type": "number"
                                        },
                                        "maxItems": 2,
                                        "minItems": 2,
                                        "description": "Latitude and longitude as two values. Example: `[51.5074, -0.1278]`"
                                    },
                                    "radius": {
                                        "type": "integer",
                                        "minimum": 5,
                                        "maximum": 30,
                                        "description": "Radius in miles, minimum 5, maximum 30. Example: `10`"
                                    },
                                    "gender": {
                                        "type": "array",
                                        "items": {
                                            "type": "string",
                                            "enum": [
                                                "Male",
                                                "Female",
                                                "Non-binary",
                                                "All"
                                            ]
                                        },
                                        "description": "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": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer"
                                        },
                                        "description": "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": {
                                        "type": "string",
                                        "format": "date-time",
                                        "description": "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": {
                                        "type": "string",
                                        "format": "date-time",
                                        "description": "ISO 8601, UTC; at least 60 seconds after `start_datetime`. Example: `\"2027-07-12T20:30:00Z\"`"
                                    },
                                    "payment_method": {
                                        "type": "string",
                                        "enum": [
                                            "E",
                                            "B"
                                        ],
                                        "description": "`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": {
                                        "type": "string",
                                        "enum": [
                                            "TEST",
                                            "LIVE"
                                        ],
                                        "description": "`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."
                                    }
                                },
                                "required": [
                                    "category_id",
                                    "title",
                                    "description",
                                    "url",
                                    "image",
                                    "location",
                                    "radius",
                                    "start_datetime",
                                    "expiry_datetime",
                                    "payment_method",
                                    "environment"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "S1: Offer created: payment link issued (`E`), or Active/Scheduled if paid from balance (`B`).\n\nE2: The image failed automated content moderation: nothing created. Choose a different image.\n\nE3: The organisation's balance is less than the offer's cost (`payment_method=B` only): top up and retry."
                    },
                    "400": {
                        "description": "E1: 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.\n\nE1: `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.\"\n\nE1: A card payment (`payment_method=E`) below Stripe's minimum for your currency: nothing created; the message names the minimum.\n\nE1: The `Idempotency-Key` header is missing. Message: \"Idempotency-Key header is required.\"\n\nE4: Offer quota limit exceeded: the user or organisation has reached its maximum number of offers.\n\nE5: No Agilis users match the targeting supplied: nothing created. Widen the radius or broaden the `gender` or `interests` targeting.\n\nE6: 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.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Authentication failure."
                    },
                    "403": {
                        "description": "E1: `environment` is not your API key's mode: nothing created or charged. Use a key created in that mode.\n\nE9: 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`.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The `Idempotency-Key` was reused while the first request is still being processed: retry shortly."
                    },
                    "422": {
                        "description": "The `Idempotency-Key` was reused with a different payload."
                    },
                    "500": {
                        "description": "Internal server error."
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                },
                "x-return-codes": {
                    "S1": "Offer created: payment link issued (`E`), or Active/Scheduled if paid from balance (`B`).",
                    "E1": "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. / `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.\" / A card payment (`payment_method=E`) below Stripe's minimum for your currency: nothing created; the message names the minimum. / The `Idempotency-Key` header is missing. Message: \"Idempotency-Key header is required.\" / `environment` is not your API key's mode: nothing created or charged. Use a key created in that mode.",
                    "E2": "The image failed automated content moderation: nothing created. Choose a different image.",
                    "E3": "The organisation's balance is less than the offer's cost (`payment_method=B` only): top up and retry.",
                    "E4": "Offer quota limit exceeded: the user or organisation has reached its maximum number of offers.",
                    "E5": "No Agilis users match the targeting supplied: nothing created. Widen the radius or broaden the `gender` or `interests` targeting.",
                    "E6": "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.",
                    "E9": "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`."
                }
            },
            "get": {
                "operationId": "listOffers",
                "summary": "View Offers",
                "description": "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`.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#viewOfferSectionid"
                },
                "parameters": [
                    {
                        "name": "environment",
                        "in": "query",
                        "required": true,
                        "description": "`test` or `live` (case-insensitive). Must be your API key's own mode. Example: `/offers?environment=test`",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "test",
                                "live",
                                "TEST",
                                "LIVE"
                            ]
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "description": "Page size. Default `20`, maximum `100`.",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100
                        }
                    },
                    {
                        "name": "cursor",
                        "in": "query",
                        "required": false,
                        "description": "Opaque token from the previous page's `next_cursor`. Omit for the first page.",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "offer_status",
                        "in": "query",
                        "required": false,
                        "description": "Lifecycle filter: `awaiting_payment`, `active`, `paused` or `ended` (case-insensitive).",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "awaiting_payment",
                                "active",
                                "paused",
                                "ended"
                            ]
                        }
                    },
                    {
                        "name": "created_after",
                        "in": "query",
                        "required": false,
                        "description": "ISO 8601, UTC. Offers created at or after this time.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    },
                    {
                        "name": "created_before",
                        "in": "query",
                        "required": false,
                        "description": "ISO 8601, UTC. Offers created at or before this time.",
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Page returned (empty `data` array if none match)."
                    },
                    "400": {
                        "description": "Invalid query parameter (`limit`, `cursor`, `offer_status` or date)."
                    },
                    "401": {
                        "description": "Invalid API key or disabled account."
                    },
                    "403": {
                        "description": "`environment` is not your API key's mode."
                    },
                    "500": {
                        "description": "Internal server error."
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/offers/{offer_id}/pause": {
            "post": {
                "operationId": "pauseOffer",
                "summary": "Pause Offer",
                "description": "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.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#pauseResumeOfferSectionid"
                },
                "parameters": [
                    {
                        "name": "offer_id",
                        "in": "path",
                        "required": true,
                        "description": "Path parameter. Example: `45`",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "S1: Offer paused successfully.\n\nE2: Offer has already ended.\n\nE3: Offer is already paused.\n\nE5: Offer is awaiting payment."
                    },
                    "401": {
                        "description": "E1: Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                },
                "x-return-codes": {
                    "S1": "Offer paused successfully.",
                    "E1": "Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode.",
                    "E2": "Offer has already ended.",
                    "E3": "Offer is already paused.",
                    "E5": "Offer is awaiting payment."
                }
            }
        },
        "/offers/{offer_id}/resume": {
            "post": {
                "operationId": "resumeOffer",
                "summary": "Resume Offer",
                "description": "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`.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#resumeOfferSectionid"
                },
                "parameters": [
                    {
                        "name": "offer_id",
                        "in": "path",
                        "required": true,
                        "description": "Path parameter. Example: `45`",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "S2: Offer resumed successfully.\n\nE2: Offer has already ended.\n\nE4: Offer is already running.\n\nE5: Offer is awaiting payment."
                    },
                    "401": {
                        "description": "E1: Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                },
                "x-return-codes": {
                    "S2": "Offer resumed successfully.",
                    "E1": "Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode.",
                    "E2": "Offer has already ended.",
                    "E4": "Offer is already running.",
                    "E5": "Offer is awaiting payment."
                }
            }
        },
        "/offers/{offer_id}/analytics": {
            "get": {
                "operationId": "getOfferAnalytics",
                "summary": "View Offer Analytics",
                "description": "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.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#analyticsSectionid"
                },
                "parameters": [
                    {
                        "name": "offer_id",
                        "in": "path",
                        "required": true,
                        "description": "Path parameter. Example: `45`",
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "interval",
                        "in": "query",
                        "required": true,
                        "description": "One of `24h`, `7d`, `30d`, `60d`, `90d` or `all`.",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "24h",
                                "7d",
                                "30d",
                                "60d",
                                "90d",
                                "all"
                            ]
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Analytics returned successfully."
                    },
                    "401": {
                        "description": "Invalid API key, disabled account, or the offer does not belong to this account or is in the other mode."
                    },
                    "500": {
                        "description": "Internal server error."
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/offers/{offer_id}": {
            "delete": {
                "operationId": "deleteOffer",
                "summary": "Delete Offer",
                "description": "Deletes an offer belonging to the authenticated account, in your API key's mode (an offer of the other mode is not found).\n\n**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).\n\n**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.",
                "tags": [
                    "Offers"
                ],
                "externalDocs": {
                    "url": "https://www.agilis.dating/api-doc#deleteOfferSectionid"
                },
                "parameters": [
                    {
                        "name": "offer_id",
                        "in": "path",
                        "required": true,
                        "description": "Path parameter. Example: `45`",
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "S1: Offer deleted successfully."
                    },
                    "401": {
                        "description": "E1: Invalid input, or \"Offer not found\": the offer does not belong to this account or is in the other mode.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "E6: 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.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "E7: The payment page could not be closed. Nothing has been deleted. Try again in a few minutes.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ReturnCode"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Rate limit exceeded: wait for the number of seconds in Retry-After.",
                        "headers": {
                            "Retry-After": {
                                "schema": {
                                    "type": "integer"
                                }
                            }
                        }
                    }
                },
                "x-return-codes": {
                    "S1": "Offer deleted successfully.",
                    "E1": "Invalid input, or \"Offer not found\": the offer does not belong to this account or is in the other mode.",
                    "E6": "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": "The payment page could not be closed. Nothing has been deleted. Try again in a few minutes."
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "apiKey": {
                "type": "apiKey",
                "in": "header",
                "name": "x-api-key",
                "description": "Created in the Ad Portal under Manage API Keys. Shown once; Roll key replaces a lost one. One key per mode (TEST or LIVE)."
            }
        },
        "schemas": {
            "ReturnCode": {
                "type": "object",
                "properties": {
                    "return_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "S1/S2 on success, E1–E9 on error. Meaning depends on the endpoint."
                    },
                    "message": {
                        "type": "string"
                    }
                }
            }
        }
    }
}