Working with Optimize

The Optimize API lets you read the optimizations running on your site and how they are performing. Use it to build reporting dashboards, sync results into other tools, or check which variation is winning.

Every endpoint is a read-only GET request.

Optimize add-on required
Optimize endpoints require an access token with the sites:read scope and a Webflow site with the Optimize add-on.
Using Optimize on a site that isn't built in Webflow?
The same endpoints work for non-Webflow sites that use Optimize. These sites accept only OAuth app tokens, and the response identifies them as optimize-site instead of webflow-site.

Choose an endpoint

Start with List Optimize IDs and List optimizations. They return the IDs that every other endpoint needs.

Core concepts

Optimize IDs

Optimize identifies your data with an optimizeId instead of a site ID. Every other endpoint takes an optimizeId in its path.

Call List Optimize IDs to see which optimizeId belongs to which site. Each entry includes a resource with the site’s siteId and displayName.

If an optimizeId matches more than one resource your token can access, other endpoints return 409 Conflict.

Optimizations

An optimization has one of three types:

  • test: A traditional A/B test. It shows different versions of your content to a share of your traffic and declares a winner once the result reaches statistical significance.
  • personalize: A manual personalization. It shows a variation to visitors who match rules you define, and it runs until you stop it. It has no winner.
  • aiOptimize: An AI-optimized test. It uses AI to deliver the best-performing variation to each visitor.

Each optimization has one or more variations. Reports for tests and AI-optimized optimizations compare each variation against the base variation.

List optimizations returns live and draft optimizations by default. Pass status to include ready, archived, or off optimizations.

The optimization type changes what each result includes:

TypeComparison fields in results
testisWinner, lift, and statisticalSignificance
aiOptimizestrength
personalizeNone

Intervals

An interval is a snapshot of an optimization’s configuration, such as which variations are live and which goal is the target goal. When you change the configuration in a way that affects results, the current interval ends and a new one starts.

Each report covers one date range inside one interval. A range that crosses from one interval into the next is rejected. To choose a range, take the start and end of an interval from List optimizations. An interval with no end is still running.

Goals

A goal defines the action that counts as a conversion. Each interval has its own goals, and exactly one is the target goal, marked isPrimary: true. The target goal is the one Optimize uses to measure success. For AI-optimized optimizations, it’s also what the AI optimizes for. Other goals are used only for reporting.

Reports measure one goal at a time. Pass goalId to choose it. If you omit goalId, the report uses the interval’s target goal. Get valid IDs from List optimization goals.

A goal’s metricType changes the response:

  • conversion: Results show conversions and conversion rate.
  • value: Results also include the total value and the average valuePerConversion.

A goal’s scope is the unit it counts conversions in: session, user, or pageview.

Reading results

Each variation in a report includes the following core metrics:

  • sessions: A period of continuous activity by a visitor. A session ends after 30 minutes of inactivity.
  • users: The number of unique visitors who saw the variation. A visitor who returns counts as one user.
  • conversions: How many times visitors took the action the goal tracks.
  • conversionRate: Conversions divided by sessions, returned as a fraction. For example, 0.1193 means 11.93%.

lift is also a fraction. A lift of 0.19 means the variation converts 19% better than the base variation.

To learn what each metric means, see Review your optimization results.

Data freshness
After you launch an optimization, initial processing takes about 30 minutes before data appears. After that, results update every minute.

Date ranges

Pass intervalStart and intervalEnd as UTC timestamps ending in Z, for example 2026-09-01T00:00:00Z. Offsets and date-only values aren’t accepted.

Two rules apply to every report:

  • intervalEnd must be later than intervalStart.
  • intervalStart can’t be earlier than 18 months ago.

Dimensions and filters

A dimension is an attribute of a visitor, such as country, device, or traffic source. Use dimensions to split results with groupBy on Get audience insights, or to narrow any report with filter.

Call List dimensions for the dimensions available in your date range. groupBy accepts any dimension except urlParameter.

filter uses bracket notation with in to include values or nin to exclude them. Use indexed brackets for multiple values:

filter[country][in][0]=US&filter[country][in][1]=GB&filter[deviceType][nin][0]=mobile

Separate dimensions combine with AND. Values within one dimension combine with OR. A filter can name at most 20 dimensions, with at most 50 values per operator and 500 values in total.

Finding values to filter by
Call List dimension values with a dimension’s id. Use each row’s value in your filter, not its label. For example, the country dimension returns GB as the value and United Kingdom as the label.

Pagination

List optimizations and List dimension values return results in pages. Use pageSize to set the page size (default 100, maximum 200). When a response includes nextCursor, pass it as cursor to get the next page. A response without nextCursor is the last page.

A cursor stays tied to the request that created it. For List optimizations, repeat the same status. For List dimension values, repeat the same dimension, date range, and filter. You can change pageSize between pages.

Rate and concurrency limits

Optimize applies two limits in addition to standard authentication:

  • Rate limit: The standard per-minute Data API rate limit, based on the site plan.
  • Concurrency limit: Each access token can have one Optimize request in flight at a time, across all Optimize endpoints.

If either limit is exceeded, the request fails with 429 Too Many Requests and a Retry-After header. Send Optimize calls one at a time, and retry after the Retry-After interval.

Errors

Optimize validates each request and returns 400 Bad Request with a code that identifies the rule that failed:

CodeMeaning
optimize_input_validationA parameter value is invalid. The details array names the failing field.
optimize_invalid_intervalintervalEnd isn’t later than intervalStart, the optimization has never run, or the date range isn’t within a single interval.
optimize_before_historical_floorintervalStart is earlier than 18 months ago.
optimize_unknown_goalgoalId doesn’t match a goal for this optimization and interval.
optimize_unknown_dimensiongroupBy, a filter key, or the dimension in the path doesn’t match a dimension for this optimization and interval.
optimize_unsupported_group_byThe groupBy dimension can’t be used to group, such as urlParameter.

Other requests can fail with these errors:

StatusCodeMeaning
403missing_scopesThe token is missing the sites:read scope.
403insufficient_permissionsThe user who authorized the token doesn’t have permission.
404resource_not_foundThe optimizeId wasn’t found.
404optimize_unknown_optimizationNo optimization matches the optimization_id.
409conflictThe optimizeId matches more than one resource your token can access.

Walkthrough: check which variation is winning

Imagine you’re reporting to a client on a hero headline A/B test. They want to know which variation is winning, and whether the result holds for visitors in the United Kingdom. This walkthrough finds the test, reads its results, and then splits them by country.

1

Find the Optimize ID

List the Optimize IDs your token can access, then find the entry whose resource.siteId matches your site.

curl --request GET \
--url 'https://api.webflow.com/beta/optimize' \
--header 'authorization: Bearer YOUR_API_TOKEN' \
--header 'accept: application/json'

The response lists each optimizeId with the site it belongs to:

{
"references": [
{
"optimizeId": "117420042",
"resource": {
"type": "webflow-site",
"siteId": "6512a0f4c2b3d4e5f6a7b8c9",
"workspaceId": "650e1f2a3b4c5d6e7f809142",
"displayName": "Hitchhiker's Guide to the Galaxy"
}
}
]
}
2

Find the optimization and its interval

List the live optimizations for that optimizeId.

curl --request GET \
--url 'https://api.webflow.com/beta/optimize/117420042/optimizations?status=live' \
--header 'authorization: Bearer YOUR_API_TOKEN' \
--header 'accept: application/json'

Find the test you want and note its optimizationId. Then choose an interval. Here, “Don’t Panic hero headline” has two intervals, and the example shows the second. It started on 2026-08-15T17:30:00.000Z and has no end, so it’s still running. Its target goal is “Towel purchase”.

{
"optimizationId": "417200971",
"name": "Don't Panic hero headline",
"optimizationType": "test",
"status": "live",
"intervals": [
{
"start": "2026-08-15T17:30:00.000Z",
"goals": [
{ "goalId": "198042421", "name": "Towel purchase", "isPrimary": true, "metricType": "value" }
]
}
]
}
3

Get the results

Request the results for a date range inside that interval. Because you omit goalId, the report measures the target goal.

curl --request GET \
--url 'https://api.webflow.com/beta/optimize/117420042/optimizations/417200971/summary?intervalStart=2026-09-01T00:00:00Z&intervalEnd=2026-09-15T00:00:00Z' \
--header 'authorization: Bearer YOUR_API_TOKEN' \
--header 'accept: application/json'

Each entry in variations is one variation. The comparison object shows how it compares with the base variation. Here, the second variation has a conversion rate of 11.9% against the base variation’s 10%, which is a lift of about 19%. It’s marked as the winner with a statistical significance of 0.97.

{
"report": "optimization_summary",
"goal": { "goalId": "198042421", "name": "Towel purchase" },
"optimizationType": "test",
"variations": [
{
"variationId": "617424201",
"name": "Base variation",
"sessions": 4100,
"conversions": 410,
"conversionRate": 0.1,
"comparison": { "isBaseline": true }
},
{
"variationId": "617424202",
"name": "Don't Panic in large, friendly letters",
"sessions": 4242,
"conversions": 506,
"conversionRate": 0.1193,
"comparison": {
"isBaseline": false,
"isWinner": true,
"lift": 0.193,
"statisticalSignificance": 0.97
}
}
]
}
4

Split the results by country

Call Get audience insights with groupBy=country to see whether the result holds across regions. The request takes the same date range and goalId options as the results endpoint.

curl --request GET \
--url 'https://api.webflow.com/beta/optimize/117420042/optimizations/417200971/audience-insights?intervalStart=2026-09-01T00:00:00Z&intervalEnd=2026-09-15T00:00:00Z&groupBy=country' \
--header 'authorization: Bearer YOUR_API_TOKEN' \
--header 'accept: application/json'

Each variation now includes one entry per country in segments. For the non-base variation, trendDirection shows how it compares with the base variation in that segment. confidence shows how much to trust that reading.

This example shows the United Kingdom segment for the non-base variation. It’s trending up with a confidence of 0.93.

{
"segment": "GB",
"segmentLabel": "United Kingdom",
"sessions": 1337,
"conversions": 191,
"conversionRate": 0.1969,
"lift": 0.6408,
"trendDirection": "up",
"confidence": 0.93
}

From here, try another endpoint to answer a different question:

To browse every endpoint, see Choose an endpoint.