REST API: Cursor-based pagination: Infinite Loop: "Next Token" is the same as the page I just polled

Solved
Highlighted

We are currently unable to page through the response as the `page_info` token in the Response Header (i.e. `Link`) is always returning the same value (taking us for an INFINITE LOOP)

The API is telling us that:

  1. there are more pages to consume (via `rel="next"` in the Response Header) and
  2. the next page is the exact same page that I just consumed (since the"Next Page Token" remains the same)

 

At `03 Jul 2020 23:43:26 GMT` I am making this exact request (using the `page_info` token from my prior request):

```
$ curl -v --location --request GET 'https://{{MYSTORE}}.myshopify.com/admin/api/2020-01/orders.json?limit=5&fields=id,%20updated_at&page...' --header 'Authorization: Basic {{MY-AUTH-TOKEN}}= '
```

The "Link" Response Header value is:
```
< Link: <https://{{MYSTORE}}.myshopify.com/admin/api/2020-01/orders.json?limit=5&fields=id%2C+updated_at&page_info=eyJkaXJlY3Rpb...>; rel="previous", <https://{{MYSTORE}}.myshopify.com/admin/api/2020-01/orders.json?limit=5&fields=id%2C+updated_at&page_info=eyJkaXJlY3Rpb...>; rel="next"
```

Notice the "Next Page Token" (i.e. `page_info` demarcated by `rel="next"`) is exactly the same as the `page_info` I provided in the first request!

We keep flipping over the pages in this book called "Shopify REST API v2020-01", but the pages never end and every page has the same content.

 

Here's a digest of my terminal output in case someone at Shopify could possibly discover it in the logs:

```

$ curl -v --location --request GET 'https://{{MYSTORE}}.myshopify.com/admin/api/2020-01/orders.json?limit=5&fields=id,%20updated_at&page_info=eyJkaXJlY3Rpb24iOiJuZXh0Iiwib3JkZXIiOiJ1cGRhdGVkX2F0IGFzYyIsInN0YXR1cyI6ImFueSIsInVwZGF0ZWRfYXRfbWluIjoiMjAyMC0wNy0wMiAxNDoyNjozOCBVVEMiLCJ1cGRhdGVkX2F0X21heCI6IjIwMjAtMDctMDIgMTY6MTA6MzggVVRDIiwibGFzdF9pZCI6MjUwMDE5ODIwMzUwMSwibGFzdF92YWx1ZSI6IjIwMjAtMDctMDIgMTQ6MjY6MzgifQ' --header 'Authorization: Basic {{MY-AUTH-TOKEN}}= '

 
> GET /admin/api/2020-01/orders.json?limit=5&fields=id,%20updated_at&page_info=eyJkaXJlY3Rpb24iOiJuZXh0Iiwib3JkZXIiOiJ1cGRhdGVkX2F0IGFzYyIsInN0YXR1cyI6ImFueSIsInVwZGF0ZWRfYXRfbWluIjoiMjAyMC0wNy0wMiAxNDoyNjozOCBVVEMiLCJ1cGRhdGVkX2F0X21heCI6IjIwMjAtMDctMDIgMTY6MTA6MzggVVRDIiwibGFzdF9pZCI6MjUwMDE5ODIwMzUwMSwibGFzdF92YWx1ZSI6IjIwMjAtMDctMDIgMTQ6MjY6MzgifQ HTTP/1.1
< X-Sorting-Hat-PodId: 108
< X-Sorting-Hat-ShopId: 6788333
< X-Frame-Options: DENY
< X-ShopId: {{MY-STORE-ID}}
< X-ShardId: 108
< X-Stats-UserId:
< X-Stats-ApiClientId: 2417414
< X-Stats-ApiPermissionId: 6563889158
< X-Shopify-API-Terms: By accessing or using the Shopify API you agree to the Shopify API License and Terms of Use at https://www.shopify.com/legal/api-terms
< HTTP_X_SHOPIFY_SHOP_API_CALL_LIMIT: 1/80
< X-Shopify-Shop-Api-Call-Limit: 1/80
< X-Shopify-API-Version: 2020-01
< Link: <https://{{MYSTORE}}.myshopify.com/admin/api/2020-01/orders.json?limit=5&fields=id%2C+updated_at&page_info=eyJkaXJlY3Rpb24iOiJwcmV2Iiwib3JkZXIiOiJ1cGRhdGVkX2F0IGFzYyIsInN0YXR1cyI6ImFueSIsInVwZGF0ZWRfYXRfbWluIjoiMjAyMC0wNy0wMiAxNDoyNjozOCBVVEMiLCJ1cGRhdGVkX2F0X21heCI6IjIwMjAtMDctMDIgMTY6MTA6MzggVVRDIiwibGFzdF9pZCI6MjQ5MzU1MDEwMDU4OSwibGFzdF92YWx1ZSI6IjIwMjAtMDctMDIgMTQ6MjY6MzgifQ>; rel="previous", <https://{{MYSTORE}}.myshopify.com/admin/api/2020-01/orders.json?limit=5&fields=id%2C+updated_at&page_info=eyJkaXJlY3Rpb24iOiJuZXh0Iiwib3JkZXIiOiJ1cGRhdGVkX2F0IGFzYyIsInN0YXR1cyI6ImFueSIsInVwZGF0ZWRfYXRfbWluIjoiMjAyMC0wNy0wMiAxNDoyNjozOCBVVEMiLCJ1cGRhdGVkX2F0X21heCI6IjIwMjAtMDctMDIgMTY6MTA6MzggVVRDIiwibGFzdF9pZCI6MjUwMDE5ODIwMzUwMSwibGFzdF92YWx1ZSI6IjIwMjAtMDctMDIgMTQ6MjY6MzgifQ>; rel="next"
< Strict-Transport-Security: max-age=7889238
< X-Shopify-Stage: production
< X-Request-ID: 0c6b87eb-3dba-4c92-9637-7e1bb196f745
< cf-request-id: 03b8a95150000002b059247200000001
< Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< Server: cloudflare
< CF-RAY: 5ad477fbbf1b02b0-SEA
<
* Connection #0 to host {{MYSTORE}}.myshopify.com left intact
{"orders":[{"id":2493550100589,"updated_at":"2020-07-02T08:26:38-06:00"},{"id":2500670226541,"updated_at":"2020-07-02T08:26:38-06:00"},{"id":2500190208109,"updated_at":"2020-07-02T08:26:38-06:00"},{"id":2500227596397,"updated_at":"2020-07-02T08:26:38-06:00"},{"id":2500198203501,"updated_at":"2020-07-02T08:26:38-06:00"}]}* Closing connection 0


```

2 Likes
Highlighted
Tourist
3 0 1

We are having the same issue with our major customers

0 Likes
Highlighted
Shopify Partner
528 38 109

Odd, as I just tested this out with Postman now. Below is the Fiddler capture of the first couple of request/response pairs. The cursor-based paging seems to be advancing okay in my case. :( 

 

GET https://{my_shop}.myshopify.com/admin/api/2020-04/orders.json?limit=5&fields=id,%20updated_at HTTP/1.1
Authorization: Basic {my_auth}
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Host: {my_shop}.myshopify.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: _master_udr=eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWszWVdWalpqTTVOUzFtTUdVekxUUTJNVFF0T0dGalppMDROR016TmpRNU5qVTJZVGdHT2daRlJnPT0iLCJleHAiOiIyMDIyLTA2LTI0VDE2OjEwOjE0LjY2NloiLCJwdXIiOiJjb29raWUuX21hc3Rlcl91ZHIifX0%3D--4068fe267f2de487051bbd18551f94b673f829f2; _secure_admin_session_id_csrf=6450872a722a54ddd55819f3c0d70127; _secure_admin_session_id=6450872a722a54ddd55819f3c0d70127; __cfduid=d474d283195031f2c442a1f3fcd4abe0d1591975172; _orig_referrer=https%3A%2F%2F238037e5e0fad0896680083eef3fee7d%3A813501fbe190445351977b5f9d995f42%40{my_shop}.myshopify.com%2Fadmin%2Fapi%2F2020-04%2Fcustomer%2F305519113; _shopify_y=cafb78a9-ebc4-44ad-a337-92cea8377f0f; _y=cafb78a9-ebc4-44ad-a337-92cea8377f0f; _landing_page=%2Fadmin%2Fauth%2Flogin


HTTP/1.1 200 OK
Date: Wed, 08 Jul 2020 13:17:15 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
X-Sorting-Hat-PodId: 161
X-Sorting-Hat-ShopId: 3036253
Vary: Accept-Encoding
Referrer-Policy: origin-when-cross-origin
X-Frame-Options: DENY
X-ShopId: 3036253
X-ShardId: 161
X-Stats-UserId: 
X-Stats-ApiClientId: 309925
X-Stats-ApiPermissionId: 8304915
X-Shopify-API-Terms: By accessing or using the Shopify API you agree to the Shopify API License and Terms of Use at https://www.shopify.com/legal/api-terms
HTTP_X_SHOPIFY_SHOP_API_CALL_LIMIT: 1/40
X-Shopify-Shop-Api-Call-Limit: 1/40
X-Shopify-API-Version: 2020-04
Link: <https://{my_shop}.myshopify.com/admin/api/2020-04/orders.json?limit=5&fields=id%2C+updated_at&page_info=eyJsYXN0X2lkIjoxODk4MTMxMzU3NzQ4LCJsYXN0X3ZhbHVlIjoiMjAxOS0xMi0xMCAxNzo0NDo0MCIsImRpcmVjdGlvbiI6Im5leHQifQ>; rel="next"
Strict-Transport-Security: max-age=7889238
X-Request-Id: 9e47775e-381e-4142-838c-5027c6a59fe2
X-Shopify-Stage: production
Content-Security-Policy: default-src 'self' data: blob: 'unsafe-inline' 'unsafe-eval' https://* shopify-pos://*; block-all-mixed-content; child-src 'self' https://* shopify-pos://*; connect-src 'self' wss://* https://*; frame-ancestors 'none'; img-src 'self' data: blob: https:; script-src https://cdn.shopify.com https://cdn.shopifycdn.net https://checkout.shopifycs.com https://js-agent.newrelic.com https://bam.nr-data.net https://api.stripe.com https://mpsnare.iesnare.com https://appcenter.intuit.com https://www.paypal.com https://js.braintreegateway.com https://c.paypal.com https://maps.googleapis.com https://www.google-analytics.com https://v.shopify.com https://widget.intercom.io https://js.intercomcdn.com 'self' 'unsafe-inline' 'unsafe-eval'; upgrade-insecure-requests; report-uri /csp-report?source%5Baction%5D=index&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Forders&source%5Bsection%5D=admin_api&source%5Buuid%5D=9e47775e-381e-4142-838c-5027c6a59fe2
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 1; mode=block; report=/xss-report?source%5Baction%5D=index&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Forders&source%5Bsection%5D=admin_api&source%5Buuid%5D=9e47775e-381e-4142-838c-5027c6a59fe2
X-Dc: gcp-us-central1,gcp-us-central1
NEL: {"report_to":"network-errors","max_age":2592000,"failure_fraction":0.01,"success_fraction":0.0001}
Report-To: {"group":"network-errors","max_age":2592000,"endpoints":[{"url":"https://monorail-edge.shopifycloud.com/v1/reports/nel/20190325/shopify"}]}
CF-Cache-Status: DYNAMIC
cf-request-id: 03d02bd31900000380431c7200000001
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Server: cloudflare
CF-RAY: 5afa15982f3f0380-ORD
alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400
Content-Length: 297

{"orders":[{"id":2521723895970,"updated_at":"2020-06-15T16:48:10Z"},{"id":2515569279138,"updated_at":"2020-06-15T16:57:00Z"},{"id":1898535125044,"updated_at":"2019-12-10T22:36:43Z"},{"id":1898480599092,"updated_at":"2019-12-10T21:51:41Z"},{"id":1898131357748,"updated_at":"2020-06-12T17:21:22Z"}]}

------------------------------------------------------------------

GET https://{my_shop}.myshopify.com/admin/api/2020-04/orders.json?limit=5&fields=id,%20updated_at&page_info=eyJsYXN0X2lkIjoxODk4MTMxMzU3NzQ4LCJsYXN0X3ZhbHVlIjoiMjAxOS0xMi0xMCAxNzo0NDo0MCIsImRpcmVjdGlvbiI6Im5leHQifQ HTTP/1.1
Authorization: Basic {my_auth}
User-Agent: PostmanRuntime/7.26.1
Accept: */*
Host: {my_shop}.myshopify.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: _master_udr=eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWszWVdWalpqTTVOUzFtTUdVekxUUTJNVFF0T0dGalppMDROR016TmpRNU5qVTJZVGdHT2daRlJnPT0iLCJleHAiOiIyMDIyLTA2LTI0VDE2OjEwOjE0LjY2NloiLCJwdXIiOiJjb29raWUuX21hc3Rlcl91ZHIifX0%3D--4068fe267f2de487051bbd18551f94b673f829f2; _secure_admin_session_id_csrf=6450872a722a54ddd55819f3c0d70127; _secure_admin_session_id=6450872a722a54ddd55819f3c0d70127; __cfduid=d474d283195031f2c442a1f3fcd4abe0d1591975172; _orig_referrer=https%3A%2F%2F238037e5e0fad0896680083eef3fee7d%3A813501fbe190445351977b5f9d995f42%40{my_shop}.myshopify.com%2Fadmin%2Fapi%2F2020-04%2Fcustomer%2F305519113; _shopify_y=cafb78a9-ebc4-44ad-a337-92cea8377f0f; _y=cafb78a9-ebc4-44ad-a337-92cea8377f0f; _landing_page=%2Fadmin%2Fauth%2Flogin


HTTP/1.1 200 OK
Date: Wed, 08 Jul 2020 13:17:42 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
X-Sorting-Hat-PodId: 161
X-Sorting-Hat-ShopId: 3036253
Vary: Accept-Encoding
Referrer-Policy: origin-when-cross-origin
X-Frame-Options: DENY
X-ShopId: 3036253
X-ShardId: 161
X-Stats-UserId: 
X-Stats-ApiClientId: 309925
X-Stats-ApiPermissionId: 8304915
X-Shopify-API-Terms: By accessing or using the Shopify API you agree to the Shopify API License and Terms of Use at https://www.shopify.com/legal/api-terms
HTTP_X_SHOPIFY_SHOP_API_CALL_LIMIT: 1/40
X-Shopify-Shop-Api-Call-Limit: 1/40
X-Shopify-API-Version: 2020-04
Link: <https://{my_shop}.myshopify.com/admin/api/2020-04/orders.json?limit=5&fields=id%2C+updated_at&page_info=eyJkaXJlY3Rpb24iOiJwcmV2IiwibGFzdF9pZCI6MTg5NzgzOTc4ODA4NCwibGFzdF92YWx1ZSI6IjIwMTktMTItMTAgMTM6NTU6MDMifQ>; rel="previous", <https://{my_shop}.myshopify.com/admin/api/2020-04/orders.json?limit=5&fields=id%2C+updated_at&page_info=eyJkaXJlY3Rpb24iOiJuZXh0IiwibGFzdF9pZCI6MTg5Njk2NDk0ODAyMCwibGFzdF92YWx1ZSI6IjIwMTktMTItMDkgMjA6MDk6NTAifQ>; rel="next"
Strict-Transport-Security: max-age=7889238
X-Request-Id: 9057a73f-a2e2-4e8b-95fc-3c1bc116c31e
X-Shopify-Stage: production
Content-Security-Policy: default-src 'self' data: blob: 'unsafe-inline' 'unsafe-eval' https://* shopify-pos://*; block-all-mixed-content; child-src 'self' https://* shopify-pos://*; connect-src 'self' wss://* https://*; frame-ancestors 'none'; img-src 'self' data: blob: https:; script-src https://cdn.shopify.com https://cdn.shopifycdn.net https://checkout.shopifycs.com https://js-agent.newrelic.com https://bam.nr-data.net https://api.stripe.com https://mpsnare.iesnare.com https://appcenter.intuit.com https://www.paypal.com https://js.braintreegateway.com https://c.paypal.com https://maps.googleapis.com https://www.google-analytics.com https://v.shopify.com https://widget.intercom.io https://js.intercomcdn.com 'self' 'unsafe-inline' 'unsafe-eval'; upgrade-insecure-requests; report-uri /csp-report?source%5Baction%5D=index&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Forders&source%5Bsection%5D=admin_api&source%5Buuid%5D=9057a73f-a2e2-4e8b-95fc-3c1bc116c31e
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
X-XSS-Protection: 1; mode=block; report=/xss-report?source%5Baction%5D=index&source%5Bapp%5D=Shopify&source%5Bcontroller%5D=admin%2Forders&source%5Bsection%5D=admin_api&source%5Buuid%5D=9057a73f-a2e2-4e8b-95fc-3c1bc116c31e
X-Dc: gcp-us-central1,gcp-us-central1
NEL: {"report_to":"network-errors","max_age":2592000,"failure_fraction":0.01,"success_fraction":0.0001}
Report-To: {"group":"network-errors","max_age":2592000,"endpoints":[{"url":"https://monorail-edge.shopifycloud.com/v1/reports/nel/20190325/shopify"}]}
CF-Cache-Status: DYNAMIC
cf-request-id: 03d02c3e300000fd825189b200000001
Expect-CT: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
Server: cloudflare
CF-RAY: 5afa16438d5dfd82-ORD
alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400
Content-Length: 297

{"orders":[{"id":1897839788084,"updated_at":"2019-12-10T13:55:07Z"},{"id":1897838149684,"updated_at":"2020-06-15T16:59:25Z"},{"id":1897782214708,"updated_at":"2019-12-10T12:46:05Z"},{"id":1897779691572,"updated_at":"2020-06-15T18:12:24Z"},{"id":1896964948020,"updated_at":"2019-12-09T20:09:53Z"}]}

------------------------------------------------------------------

1 Like
Highlighted

@Greg_Kujawa Yes, I did failed to mention that this behavior/bug was noticed in many stores, but not all.

Hence the reason for attaching the `X-Request-Id`.

 

From what we've observed, distinguishing factor seemed to be that this mostly occurs on high volume stores, where they could have large chunks of Orders with the same `updated_at` timestamp.

 

Based on that premise, perhaps a more precise repro steps and/or prerequisite would be as follows:

Prerequisite 1: Your store must have at least 2 Orders having the same `updated_at` timestamp of `{{T1}}`

Step 1: Query the API to retrieve the Orders starting from `{{T1}}`, in order of `updated_at`, limiting the Page Size to 1 (i.e. lower than the total number of Orders having `updated_at` values of `{{T1}}`:

GET https://{{MYSTORE}}.myshopify.com/admin/api/2020-01/orders.json?status=any&limit=1&updated_at_min=2{{T1}}&order=updated_at%20asc

Step 2: Capture the "Next Page Token" from the "Link" Header and make the subsequent request.

Step 3: Repeat Step 2 and compare output from Step 2 and 3. If they are the same, then we have an INFINITE LOOP/Pages.

 

Thx for taking a look.

0 Likes
Highlighted
Shopify Partner
528 38 109

Very interesting! Now I see wherein the bug lies. Looks like some folks at Shopify should possibly visit some of the tips listed here --> https://engineering.mixmax.com/blog/api-paging-built-the-right-way/

1 Like
Highlighted

@Greg_Kujawa Thx for verifying the repro!

It's good to know that I wasn't seeing ghosts. ^_^

0 Likes
Highlighted
Tourist
3 0 1

Confirming. It's only some stores and those have multiple orders updated at the same time. (Maybe originally this wasn't a cursor-based pagination error, but maybe some orders got their update_at at updated at at the same time by Shopify automatically? We noticed thousands of these updates in our stores) Is Shopify going to resolve this?

1 Like
Highlighted
New Member
1 0 0

Hello @Greg_Kujawa,

I am also facing the same issue where Shopify Orders API keeps on returning the same orders page in Pagination loop. Below is our scenario;

Total Orders Count: 1607
API Limit Set to Pull Orders: 250 Orders

Total Pages: 7

API Endpint: GET /orders.json
API Request IDs: 9667d16d-6401-4135-9a0c-f3d140b81fa0, 5bded19d-30b4-4325-87c9-babfe6c90c68

Starting from request / page no. 4 we start getting same 'next' link or next page with same orders. This goes in infinite loop. We have also found that it happens when there are large transactions and after certain pagination i.e. in some case it is page no. 4, in some it could be 6, etc we get same 'next' link.

We have tested this scenario with multiple stores and are getting same issue.

Please help. 

Thanks,

Amol

0 Likes
Highlighted
Shopify Partner
528 38 109

Sounds like there are some resource or RI constraints on the Shopify end of things. If a record is loaded into their cursor based on both timestamp (which should be dropped down to milliseconds optimally) and its record ID then that might help shore things up. But then again, the logic and mechanics behind the new cursor-based pagination is an unknown to the API consumer

Hopefully someone from Shopify chimes in, since this bug can definitely affect a lot of partner apps and custom/private apps for larger consumers with lots of transaction load...

0 Likes
Highlighted

This is an accepted solution.

// UPDATE: I've been notified by `Shopify` they have patched the issue:


R**** C (Shopify)

 

Jul 29, 2020, 4:28 AM EDT

 

I wanted to touch base and let you know our developers have pushed a fix for this issue on our end. You should no longer be seeing any issues when using cursor based pagination when multiple orders have the same `updated_at`/`created_at` times.

 

If you have any issues or if there's anything else we can assist with please let me know.

 

Best regards,

 

R**** C.
Technical Merchant Support - Plus
Shopify

I've at least confirmed the "patch" on all stores I work with.

Thank you all for reporting, identifying and verifying the issue.

Closing as Resolved.

0 Likes