This guide explains how to move an existing AdFlex API integration from V1 to V2.
V2 keeps the standard response envelope used by V1, but it changes endpoint paths, authentication, filter metadata, sorting, search-result fields, and ad-detail responses. Do not migrate by changing only v1 to v2 in the URL.
| Area | V1 | V2 | Required action |
|---|---|---|---|
| Base URL | https://api.adflex.io/api/v1 | https://api.adflex.io/api with routes beginning with /v2 | Update every route using the endpoint map below |
| Authentication | API key sent in the x-api-key header or as api_key in the query string or request body | Formal contract uses the x-api-key header | Keep or move the key to the request header |
| Filter routes | Separate /filters and /filter route patterns | All filter routes are under /v2/ads/{platform}/filters | Update filter discovery and filter-item requests |
| Filter metadata | Component settings nested under component_data | Component settings are flattened onto each filter | Update dynamic-filter parsing |
| Sorting | A single orderby string | An advanced_order object containing orderby and order | Map the old sorting value to the new structure |
| Search results | Platform-specific fields | Normalized cards using platform, stats, and metadata | Update search-result models and UI mapping |
| Ad details | Ad data nested under data.data.ad | Structured sections directly under data | Replace the V1 detail-response parser |
| Pagination | page and last_hit | Snapshot pagination with stricter last_hit handling | Preserve the original page-one last_hit value |
| Errors | Application codes in the response body | HTTP status is always 200; application result remains in the response body | Check status and meta.code for every response |
| Aggregated search | Not available | mega searches across platforms | Use the source platform for detail requests |
For V2, send your API key in the x-api-key header on every request:
x-api-key: YOUR_API_KEYDo not place API keys in URLs, logs, analytics events, or client-side code. If your current key is not enabled for V2, contact AdFlex support before continuing the migration.
GET /api/v1/filters/facebook/search?api_key=YOUR_API_KEYGET /api/v2/ads/facebook/filters
x-api-key: YOUR_API_KEYAlthough compatibility authentication methods may be accepted, the V2 OpenAPI contract uses x-api-key. New and migrated integrations should use the header.
The following patterns apply to all individual platform values:
facebookmetanativedisplaypinterestreddittiktokxyoutube
| Operation | V1 | V2 |
|---|---|---|
| Get filters | GET /api/v1/filters/{platform}/search | GET /api/v2/ads/{platform}/filters |
| Get dynamic filter items | GET /api/v1/{platform}/filter/{filter}/items | GET /api/v2/ads/{platform}/filters/{filter} |
| Search ads | POST /api/v1/{platform}/ads/search | POST /api/v2/ads/{platform}/search |
| Get ad details | GET /api/v1/{platform}/ads/{ad} | GET /api/v2/ads/{platform}/{ad} |
The complete V2 request URL is the base URL plus the V2 route. For example:
https://api.adflex.io/api/v2/ads/facebook/searchOnly call a dynamic filter-item route when the filter response provides an api_url. Send the search term as a query parameter:
curl --get 'https://api.adflex.io/api/v2/ads/facebook/filters/interests' \
--header 'x-api-key: YOUR_API_KEY' \
--data-urlencode 'search=shopping'The V1 ids query parameter is not part of the V2 public contract. Remove it from migrated requests unless AdFlex has confirmed a separate compatibility requirement for your account.
V1 places most component settings inside component_data. V2 places the applicable settings directly on each filter and omits fields that do not apply.
{
"key": "interests",
"component": "StringMultiApi",
"component_data": {
"api_url": "v1/facebook/filter/interests/items",
"items": [],
"min": null,
"max": null
},
"resettable": true
}{
"key": "interests",
"component": "StringMultiApi",
"api_url": "v2/ads/facebook/filters/interests",
"items": [],
"default": []
}Update your parser as follows:
| V1 location | V2 location or behavior |
|---|---|
component_data.api_url | api_url |
component_data.items | items |
component_data.min | min |
component_data.max | max |
component_data.infinite_sign | infinite_sign |
component_data.action and action_data | Not part of the V2 public filter schema |
relations | Not part of the V2 FilterGroup schema |
resettable | Not part of the V2 public filter schema |
disabled | Not part of the V2 public filter schema |
| No equivalent | disable_other, when applicable |
Do not assume that optional fields exist or contain null. V2 generally omits fields that are not applicable.
Fetch the V2 filter definition for each platform during migration. Do not build a V2 request by copying cached V1 filter metadata.
Remove api_key from the JSON request body and send it in the x-api-key header.
Replace the V1 orderby string with the V2 advanced_order object:
{
"advanced_order": {
"orderby": "newest",
"order": "desc"
}
}Use the following mapping when converting existing saved searches:
V1 orderby | V2 advanced_order |
|---|---|
updated_at | { "orderby": "newest", "order": "desc" } |
oldest | { "orderby": "newest", "order": "asc" } |
popularity | { "orderby": "engagement", "order": "desc" } |
most_relevant | { "orderby": "relevancy", "order": "desc" } |
seen_counts | { "orderby": "ad_impressions", "order": "desc" } |
impressions | { "orderby": "ad_impressions", "order": "desc" } |
days_active | { "orderby": "days_running", "order": "desc" } |
running_longest | { "orderby": "days_running", "order": "desc" } |
domain.traffic | { "orderby": "web_traffic", "order": "desc" } |
Not every sort option is available for every platform. Before sending a saved sort, confirm that its V2 orderby value appears in the advanced_order.items array returned by that platform's filter endpoint. If it is unavailable, use the platform's default sort.
curl --request POST 'https://api.adflex.io/api/v2/ads/facebook/search' \
--header 'Content-Type: application/json' \
--header 'x-api-key: YOUR_API_KEY' \
--data '{
"page": 1,
"advanced_order": {
"orderby": "newest",
"order": "desc"
},
"target_age": [18, 65],
"gender": [9886],
"interests": [10, 4],
"search_field": [
{ "type": "text", "text": "PlayStation" },
{ "type": "without_owner", "text": "MSI" }
]
}'Most platform-specific filter keys remain recognizable, but use the V2 filter endpoint as the source of truth for accepted keys, values, defaults, and component types.
V2 search responses return up to 18 ads per page. The page size is fixed.
For the first page:
- Set
pageto1. - Omit
last_hit. - Store the returned
data.last_hitvalue.
For the second and later pages:
- Increment
page. - Send the exact
data.last_hitvalue returned by the first-page response. - Keep all filters and sorting options unchanged.
- Continue while
data.has_next_pageistrue.
{
"page": 2,
"last_hit": 1786180640,
"advanced_order": {
"orderby": "newest",
"order": "desc"
}
}Do not generate, increment, or otherwise modify last_hit. If any filter or sorting option changes, restart at page 1 and omit the previous value. V2 does not impose an expiry time or maximum page depth for last_hit.
The outer response envelope remains the same:
{
"status": "ok",
"meta": {
"code": 1000,
"message": null
},
"data": {}
}Search pagination also remains under data:
data.adsdata.current_pagedata.has_next_pagedata.last_hit
The objects inside data.ads have been normalized in V2. Update any serializer, database mapper, UI component, or export job that reads V1 search fields.
| V1 search field | V2 replacement or behavior |
|---|---|
id | id |
| No common field | platform identifies the result's source platform |
display_site_url, display_url, or url | display_url, when available for the platform |
updated_at | last_seen, a display-oriented relative value such as 10 minutes ago |
active_days | Usually represented in metadata; exact values are available in the detail response under impression.days_running |
engagements | stats, when available |
views, seen_counts, or impressions | stats or metadata, depending on the platform and metric |
locations and placements | metadata |
is_active | active in Meta search results |
attachments[].files.main | attachments[].url for image and video platforms |
attachments[].files.thumbnail | attachments[].thumbnail |
attachments[].files.youtube_id | attachments[].youtube_id for YouTube |
The V2 stats and metadata fields are arrays of display-ready label-and-value entries. Do not depend on array order, and allow entries to be absent when a metric does not apply to a platform.
If your application needs an exact last-seen timestamp, do not parse the relative last_seen search value. Request the ad details and use data.impression.last_seen_at.
Owner and attachment objects are also normalized. For example, Native and Display owner objects now use name and avatar_url instead of the V1 domain, title, and icon combination. Update platform-specific owner adapters rather than assuming that every V1 owner field is preserved.
The V2 detail response is a structural redesign.
{
"data": {
"data": {
"ad": {},
"durations": [],
"url_chains": [],
"domain": {},
"technologies": []
}
}
}{
"data": {
"id": 100,
"post_url": "https://example.com/ad",
"card": {},
"impression": {},
"targeting_detail": {},
"engagement_data": {},
"url_chain": [],
"domain": {}
}
}The exact sections vary by platform. Use optional fields and platform-specific models.
| V1 detail location | V2 location |
|---|---|
data.data.ad.id | data.id |
data.data.ad.ad_url | data.post_url, when available |
data.data.ad.title | data.card.title |
data.data.ad.cta | data.card.cta |
data.data.ad.is_bookmarked | data.card.is_bookmarked |
data.data.ad.owner | data.card.owner |
data.data.ad.attachments | data.card.attachments |
data.data.ad.impressions | data.impression.total |
data.data.ad.active_days | data.impression.days_running |
data.data.ad.first_seen_at | data.impression.first_seen_at |
data.data.ad.last_seen_at | data.impression.last_seen_at |
Targeting fields on data.data.ad | data.targeting_detail.cards and data.targeting_detail.tabs |
data.data.ad.engagements | data.engagement_data, when available |
data.data.url_chains | data.url_chain |
data.data.domain | data.domain, using the new domain schema |
data.data.technologies | No equivalent top-level field in the V2 public contract |
The V2 domain object also changes:
| V1 domain field | V2 domain field |
|---|---|
domain | name |
monthly_traffic | monthly_visit |
daily_traffic | No direct equivalent |
domain_created_at, domain_updated_at, and domain_expires_at | No direct equivalent in the V2 public contract |
| No direct V1 equivalent | age and timeline |
Do not make engagement_data, publishers, impression_data, or impression_breakdown mandatory in a shared model. These sections are platform-specific.
V2 returns HTTP 200 OK for successful and failed application responses. A transport-level success does not mean that the API operation succeeded.
A successful response normally contains:
{
"status": "ok",
"meta": {
"code": 1000,
"message": null
}
}A failed response can contain:
{
"status": "failed",
"meta": {
"code": 1009,
"message": "Server Error"
},
"data": []
}Treat a response as successful only when both conditions are true:
statusisokmeta.codeis1000
Handle unknown non-1000 codes safely instead of assuming that the documented list is exhaustive. See API Response Codes for the current code list.
V2 credit usage is request-based:
| Endpoint | Cost |
|---|---|
| Get filters | Free |
| Get dynamic filter items | Free |
| Search ads | 100 credits |
| Get ad details | 100 credits |
The number of results in a response does not change the cost.
V2 has no predefined per-second or per-minute rate limit. Retry only connection errors, timeouts, or temporary server failures. Use exponential backoff with jitter and stop after three attempts. Do not retry validation, authentication, permission, or not-found failures.
Every retry is another API request and can consume credits when the endpoint is not free. Include retry traffic and parallel V1/V2 testing in your migration credit estimate.
V2 adds mega as an aggregated search source:
GET /api/v2/ads/mega/filters
POST /api/v2/ads/mega/searchMega results include a platform value. Mega does not provide a detail endpoint. To retrieve details, use the result's source platform and ad ID:
Search result: platform = facebook, id = 123
Detail route: GET /api/v2/ads/facebook/123The following route does not exist:
GET /api/v2/ads/mega/{ad}- Inventory every V1 endpoint, platform, filter, sort, response field, export, and background job used by your integration.
- Add a separate V2 client or version-specific adapter. Keep the working V1 client unchanged during validation.
- Move authentication to the
x-api-keyheader and verify the key against a free V2 filter request. - Replace all endpoint paths using the mapping in this guide.
- Fetch fresh V2 filter metadata and update the filter parser for flattened fields and omitted optional values.
- Convert saved V1 sorts to
advanced_orderand fall back when a platform does not support the requested sort. - Update search-result and detail-response models. Keep platform-specific sections optional.
- Implement V2 snapshot pagination and response-body error handling.
- Test filters, page-one search, later pages, ad details, failures, and credit behavior for every platform you use.
- Run a limited parallel comparison between V1 and V2. Exact result order and counts may change as live data is collected, so validate structure and business behavior rather than requiring byte-for-byte equality.
- Roll out V2 behind a feature flag or gradual traffic split, monitor failures and credit consumption, and keep a tested V1 rollback path during the cutover window.
- After V2 is stable, remove V1-specific routes, request fields, parsers, and any API keys stored in query strings or request bodies.
- Every request uses an
/api/v2/ads/...route. - The API key is sent in the
x-api-keyheader. - API keys are absent from URLs, request bodies, logs, and analytics.
- Dynamic filter parsing no longer depends on
component_data. - Optional filter fields may be omitted instead of set to
null. - Saved
orderbyvalues are converted toadvanced_order. - Each sort is checked against the selected platform's V2 filter response.
- Page 1 omits
last_hit. - Later pages reuse the original page-one
last_hitwithout modifying it. - Search-card parsing supports
platform,stats, andmetadata. - Exact timestamps come from the detail response, not the relative search value.
- Detail parsing starts at
data, notdata.data.ad. - Platform-specific detail sections are optional.
- Application success requires
status = okandmeta.code = 1000. - Failed application responses are handled even when HTTP status is
200. - Search, detail, and retry traffic is included in credit monitoring.
- Mega detail requests use the source platform rather than
mega. - V2 has been tested for every platform used in production.
- A rollback path remains available until the V2 rollout is complete.