## TL;DR
An opaque cursor is a token, not a number. Take the exact string the provider returned and send it back unchanged - no parsing it as an integer, no incrementing, no decoding. The 400 invalid cursor disappears the moment the client stops interpreting a value it was never meant to read.

## The query

```text
generated client treated the opaque cursor as a page number  -  requests came back 400 invalid cursor
```

## Steps

### 1. Find where the client interprets the cursor

Search the generated pagination code for any operation on the cursor value: integer conversion, arithmetic, string splitting, base64 decoding, or formatting it into a page-number parameter. That line is the bug.

Expected: one identified location where the cursor is transformed instead of passed through.

### 2. Replace interpretation with pass-through

Store the cursor as an opaque string and send it back in exactly the parameter the provider documents. No `int()` conversion, no `+ 1`, no decoding. If the provider names the parameter `cursor`, the client sends `cursor` with the untouched string.

Expected: pagination code with zero transformations applied to the cursor value.

### 3. Fix the initial-page call

The first page usually takes no cursor or an explicit empty value. Make sure the client does not invent a cursor of `0` or `1` for the first request - that is the same bug in a different place.

Expected: the first request sends no cursor (or the provider's documented initial value), subsequent requests echo the returned cursor.

### 4. Re-run the full pagination and watch for 400s

Paginate a real list end to end and confirm no 400 invalid cursor responses. Log the cursor on each request during the test so any future transformation is visible.

Expected: a complete paginated fetch with zero invalid-cursor errors.

### 5. Type the cursor as opaque in the generated code

Declare the cursor as a string type with a comment marking it opaque and provider-defined. The next regeneration sees the annotation and does not "helpfully" convert it to a number.

Expected: a type annotation plus comment that survives regeneration.

## Use this when

- The provider 400s with "invalid cursor" on page two onward
- The generated code converts the cursor to an integer or increments it
- The first page works and every subsequent page fails
- Cursor values in logs look truncated, rounded, or renumbered

## Not for this skill when

- The cursor is expired server-side (time-based invalidation, different fix)
- The cursor is double-URL-encoded (encoding problem)
- The endpoint genuinely uses numeric page parameters (then page numbers are correct)
- The 400 names a different parameter (read the error body carefully)

## Variant phrasings

### 400 invalid cursor on the second page

The first page needed no cursor, so the bug only shows on page two. Step 1 finds the transformation; step 2 removes it.

### cursor looks like a number so the client parsed it as one

Some opaque cursors are numeric-looking strings. Looks-like-a-number is not is-a-number: step 5's opaque-string typing prevents the next agent from making the same inference.

## Why it happens

Cursors that look like `1683749200` invite arithmetic, and the agent's training data is full of offset pagination where page numbers increment. The agent pattern-matched "thing that changes per page" to "page number" and generated increment logic. The provider's cursor is an internal bookmark - possibly encrypted or hashed - and any modification invalidates it. The 400 is the provider saying "I never issued this value."

## Edge cases

- Provider documents the cursor as base64: still do not decode it. "Base64-encoded" describes the transport, not an invitation to interpret the contents.
- Cursor contains characters needing URL encoding: encode it once for transport (that is not interpretation), and never double-encode.
- Mixed pagination styles across endpoints: type each endpoint's cursor separately. One endpoint's page numbers do not make another endpoint's cursor numeric.
- Client caches cursors across runs: persist the exact string. A cached cursor that was transformed before storage is already broken.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_jYwjtSxj-s9iJ4vXmYAZBw
