Skip to content
Last updated

Migrate from API V1 to V2

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.

Migration overview

AreaV1V2Required action
Base URLhttps://api.adflex.io/api/v1https://api.adflex.io/api with routes beginning with /v2Update every route using the endpoint map below
AuthenticationAPI key sent in the x-api-key header or as api_key in the query string or request bodyFormal contract uses the x-api-key headerKeep or move the key to the request header
Filter routesSeparate /filters and /filter route patternsAll filter routes are under /v2/ads/{platform}/filtersUpdate filter discovery and filter-item requests
Filter metadataComponent settings nested under component_dataComponent settings are flattened onto each filterUpdate dynamic-filter parsing
SortingA single orderby stringAn advanced_order object containing orderby and orderMap the old sorting value to the new structure
Search resultsPlatform-specific fieldsNormalized cards using platform, stats, and metadataUpdate search-result models and UI mapping
Ad detailsAd data nested under data.data.adStructured sections directly under dataReplace the V1 detail-response parser
Paginationpage and last_hitSnapshot pagination with stricter last_hit handlingPreserve the original page-one last_hit value
ErrorsApplication codes in the response bodyHTTP status is always 200; application result remains in the response bodyCheck status and meta.code for every response
Aggregated searchNot availablemega searches across platformsUse the source platform for detail requests

1. Update authentication

For V2, send your API key in the x-api-key header on every request:

x-api-key: YOUR_API_KEY

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

V1

GET /api/v1/filters/facebook/search?api_key=YOUR_API_KEY

V2

GET /api/v2/ads/facebook/filters
x-api-key: YOUR_API_KEY

Although compatibility authentication methods may be accepted, the V2 OpenAPI contract uses x-api-key. New and migrated integrations should use the header.

2. Update endpoint paths

The following patterns apply to all individual platform values:

  • facebook
  • meta
  • native
  • display
  • pinterest
  • reddit
  • tiktok
  • x
  • youtube
OperationV1V2
Get filtersGET /api/v1/filters/{platform}/searchGET /api/v2/ads/{platform}/filters
Get dynamic filter itemsGET /api/v1/{platform}/filter/{filter}/itemsGET /api/v2/ads/{platform}/filters/{filter}
Search adsPOST /api/v1/{platform}/ads/searchPOST /api/v2/ads/{platform}/search
Get ad detailsGET /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/search

Dynamic filter items

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

3. Update filter-response parsing

V1 places most component settings inside component_data. V2 places the applicable settings directly on each filter and omits fields that do not apply.

V1 filter object

{
  "key": "interests",
  "component": "StringMultiApi",
  "component_data": {
    "api_url": "v1/facebook/filter/interests/items",
    "items": [],
    "min": null,
    "max": null
  },
  "resettable": true
}

V2 filter object

{
  "key": "interests",
  "component": "StringMultiApi",
  "api_url": "v2/ads/facebook/filters/interests",
  "items": [],
  "default": []
}

Update your parser as follows:

V1 locationV2 location or behavior
component_data.api_urlapi_url
component_data.itemsitems
component_data.minmin
component_data.maxmax
component_data.infinite_signinfinite_sign
component_data.action and action_dataNot part of the V2 public filter schema
relationsNot part of the V2 FilterGroup schema
resettableNot part of the V2 public filter schema
disabledNot part of the V2 public filter schema
No equivalentdisable_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.

4. Update search requests

Authentication field

Remove api_key from the JSON request body and send it in the x-api-key header.

Sorting

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

Search example

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.

5. Update pagination

V2 search responses return up to 18 ads per page. The page size is fixed.

For the first page:

  • Set page to 1.
  • Omit last_hit.
  • Store the returned data.last_hit value.

For the second and later pages:

  • Increment page.
  • Send the exact data.last_hit value returned by the first-page response.
  • Keep all filters and sorting options unchanged.
  • Continue while data.has_next_page is true.
{
  "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.

6. Update search-response models

The outer response envelope remains the same:

{
  "status": "ok",
  "meta": {
    "code": 1000,
    "message": null
  },
  "data": {}
}

Search pagination also remains under data:

  • data.ads
  • data.current_page
  • data.has_next_page
  • data.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 fieldV2 replacement or behavior
idid
No common fieldplatform identifies the result's source platform
display_site_url, display_url, or urldisplay_url, when available for the platform
updated_atlast_seen, a display-oriented relative value such as 10 minutes ago
active_daysUsually represented in metadata; exact values are available in the detail response under impression.days_running
engagementsstats, when available
views, seen_counts, or impressionsstats or metadata, depending on the platform and metric
locations and placementsmetadata
is_activeactive in Meta search results
attachments[].files.mainattachments[].url for image and video platforms
attachments[].files.thumbnailattachments[].thumbnail
attachments[].files.youtube_idattachments[].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.

7. Replace the ad-detail parser

The V2 detail response is a structural redesign.

V1 structure

{
  "data": {
    "data": {
      "ad": {},
      "durations": [],
      "url_chains": [],
      "domain": {},
      "technologies": []
    }
  }
}

V2 structure

{
  "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 locationV2 location
data.data.ad.iddata.id
data.data.ad.ad_urldata.post_url, when available
data.data.ad.titledata.card.title
data.data.ad.ctadata.card.cta
data.data.ad.is_bookmarkeddata.card.is_bookmarked
data.data.ad.ownerdata.card.owner
data.data.ad.attachmentsdata.card.attachments
data.data.ad.impressionsdata.impression.total
data.data.ad.active_daysdata.impression.days_running
data.data.ad.first_seen_atdata.impression.first_seen_at
data.data.ad.last_seen_atdata.impression.last_seen_at
Targeting fields on data.data.addata.targeting_detail.cards and data.targeting_detail.tabs
data.data.ad.engagementsdata.engagement_data, when available
data.data.url_chainsdata.url_chain
data.data.domaindata.domain, using the new domain schema
data.data.technologiesNo equivalent top-level field in the V2 public contract

The V2 domain object also changes:

V1 domain fieldV2 domain field
domainname
monthly_trafficmonthly_visit
daily_trafficNo direct equivalent
domain_created_at, domain_updated_at, and domain_expires_atNo direct equivalent in the V2 public contract
No direct V1 equivalentage and timeline

Do not make engagement_data, publishers, impression_data, or impression_breakdown mandatory in a shared model. These sections are platform-specific.

8. Handle errors using the response body

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:

  • status is ok
  • meta.code is 1000

Handle unknown non-1000 codes safely instead of assuming that the documented list is exhaustive. See API Response Codes for the current code list.

9. Review credits and retries

V2 credit usage is request-based:

EndpointCost
Get filtersFree
Get dynamic filter itemsFree
Search ads100 credits
Get ad details100 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.

10. Use Mega search when appropriate

V2 adds mega as an aggregated search source:

GET /api/v2/ads/mega/filters
POST /api/v2/ads/mega/search

Mega 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/123

The following route does not exist:

GET /api/v2/ads/mega/{ad}
  1. Inventory every V1 endpoint, platform, filter, sort, response field, export, and background job used by your integration.
  2. Add a separate V2 client or version-specific adapter. Keep the working V1 client unchanged during validation.
  3. Move authentication to the x-api-key header and verify the key against a free V2 filter request.
  4. Replace all endpoint paths using the mapping in this guide.
  5. Fetch fresh V2 filter metadata and update the filter parser for flattened fields and omitted optional values.
  6. Convert saved V1 sorts to advanced_order and fall back when a platform does not support the requested sort.
  7. Update search-result and detail-response models. Keep platform-specific sections optional.
  8. Implement V2 snapshot pagination and response-body error handling.
  9. Test filters, page-one search, later pages, ad details, failures, and credit behavior for every platform you use.
  10. 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.
  11. 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.
  12. After V2 is stable, remove V1-specific routes, request fields, parsers, and any API keys stored in query strings or request bodies.

Migration verification checklist

  • Every request uses an /api/v2/ads/... route.
  • The API key is sent in the x-api-key header.
  • 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 orderby values are converted to advanced_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_hit without modifying it.
  • Search-card parsing supports platform, stats, and metadata.
  • Exact timestamps come from the detail response, not the relative search value.
  • Detail parsing starts at data, not data.data.ad.
  • Platform-specific detail sections are optional.
  • Application success requires status = ok and meta.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.