Pagination and rate limits#
Doctor searches use forward-only cursor pagination. A response contains:
{
"page": {
"count": 25,
"hasMore": true,
"nextCursor": "eyJhZnRlciI6…"
}
}
Pass nextCursor unchanged in the next request. A cursor is bound to its
country, city, text, WhatsApp, and verification filters. Changing a filter
while reusing the cursor returns 400.
const endpoint = new URL('https://www.doctosan.com/api/v1/doctors')
endpoint.searchParams.set('country', 'es')
endpoint.searchParams.set('city', 'madrid')
endpoint.searchParams.set('whatsapp', 'true')
endpoint.searchParams.set('limit', '100')
let cursor
do {
if (cursor) endpoint.searchParams.set('cursor', cursor)
const response = await fetch(endpoint, {
headers: { 'X-API-Key': process.env.DOCTOSAN_API_KEY },
})
if (response.status === 429) {
const retryAfter = Number(response.headers.get('Retry-After') || 1)
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
continue
}
if (!response.ok) throw new Error(`Doctosan API ${response.status}`)
const page = await response.json()
await persistCandidates(page.doctors)
cursor = page.page.nextCursor || undefined
} while (cursor)
Cursor rules#
- Treat cursors as opaque; their encoding can change.
- Do not persist them as durable record identifiers.
- Stop when
nextCursorisnull, regardless ofcount. - A deleted or newly published record can shift a later traversal; deduplicate
durable imports by doctor
id.
Rate limits#
The default authenticated limit is 300 requests per 60 seconds per API key. Read the response headers instead of hard-coding that number: Doctosan may assign a different limit to a key. The limiter is shared across concurrent API instances.