Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

API tutorial

How to Page Through a People Search (in Java)

One filter in, one JSON Lines file out. Java 17, no dependencies, under 60 lines.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

You page through a LeadOcean people search by sending meta.nextCursor back as cursor until it comes back null. The Java below does that with the built-in HttpClient, retries on 429 and writes one JSON line per page.

TL;DR

  • Java 17 and java.net.http.HttpClient. No Maven dependencies.
  • About 15 minutes to set up. A run is limited by the plan's rate limit, not by the code.
  • It spends one LeadOcean record per person returned. The Free plan has 1,000 records for life, so cap the test at 3 pages.

Prerequisites

  • JDK 17 or later (java -version).
  • A LeadOcean API key from app.leadocean.io. Free needs no card.
  • The key in your shell as $LEADOCEAN_API_KEY.
bash
export LEADOCEAN_API_KEY="your-key-here"

Step 1: Size the search for free

Count first. A search with count=true as a query parameter and limit 1 costs no records and returns meta.total, capped at 100,000.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"title":["VP Sales"],"country":["US"],"limit":1}'

Enum filters such as jobLevel take exact strings. List them with GET /v1/enums before you guess. One search pages to 10,000 rows, then stops, so narrow the filters if the total is bigger.

Step 2: Write Pager.java

The request body is flat JSON. Filters are arrays, limit runs from 1 to 100, and cursor carries the position. Save this as Pager.java. The key comes from the environment, never from source.

java
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
import java.util.*;
import java.util.regex.*;

public class Pager {
  static final HttpClient HTTP = HttpClient.newHttpClient();
  static final String KEY = System.getenv("LEADOCEAN_API_KEY");
  static final Pattern NEXT = Pattern.compile("\"nextCursor\"\\s*:\\s*\"([^\"]+)\"");

  static String page(String cursor) throws Exception {
    String body = "{\"title\":[\"VP Sales\"],\"country\":[\"US\"],\"limit\":25"
        + (cursor == null ? "" : ",\"cursor\":\"" + cursor + "\"") + "}";
    HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.leadocean.io/v1/people/search"))
        .header("x-api-key", KEY).header("content-type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(body)).build();
    for (int attempt = 0; attempt < 5; attempt++) {
      HttpResponse<String> res = HTTP.send(req, HttpResponse.BodyHandlers.ofString());
      if (res.statusCode() == 200) return res.body().replaceAll("\\s*\\n\\s*", " ").strip();
      if (res.statusCode() == 429) {                       // above the plan's rate limit
        long wait = Long.parseLong(res.headers().firstValue("Retry-After").orElse("1"));
        Thread.sleep(wait * 1000);
        continue;
      }
      throw new IllegalStateException(res.statusCode() + " " + res.body());
    }
    throw new IllegalStateException("still rate limited");
  }

  public static void main(String[] args) throws Exception {
    int maxPages = args.length > 0 ? Integer.parseInt(args[0]) : 3;   // test small first
    List<String> out = new ArrayList<>();
    String cursor = null;
    for (int n = 0; n < maxPages; n++) {
      String json = page(cursor);
      out.add(json);
      Matcher m = NEXT.matcher(json);
      if (!m.find()) break;                                // null cursor: the walk is over
      cursor = m.group(1);
      Thread.sleep(200);                                   // 5 a second, the Free limit
    }
    Files.write(Path.of("people.jsonl"), out);
    System.out.printf("%d pages written%n", out.size());
  }
}

Run it with the single-file launcher. The argument is the page cap:

bash
java Pager.java 3

Step 3: Handle rate limits and cursors

The Free plan allows 5 requests a second per account. Solo allows 50 and Agency allows 200. Above that the API answers 429 with a Retry-After header. The code sleeps for that many seconds and tries again, up to 5 times.

The fixed Thread.sleep(200) keeps you under the Free limit. On Agency, drop it to 5 milliseconds. Do not run pages in parallel: each cursor comes from the previous response.

Send meta.nextCursor back exactly as received. Cursors are signed, and an edited one is refused with 400. Do not store a cursor for later. Restart the query instead.

The walk ends when nextCursor is null. On a search that hits the 10,000 row depth cap, the last page carries meta.depthCapped: true and no cursor. The answer is narrower filters and several smaller searches.

Step 4: Write results where you can use them

The output is JSON Lines: one full page per line. Read it with any JSON library, or with jq:

bash
jq -c '.data[]' people.jsonl > people-flat.jsonl
jq -r '.meta | [.count, .nextCursor != null] | @csv' people.jsonl

Search returns a thin projection with no email or phone. To get contact data, enrich the person_id values you want with POST /v1/people/enrich. For a whole list as a file, use POST /v1/exports instead of paging. It takes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free.

What you get

This is the response shape from the API reference, with placeholder values.

json
{
  "success": true,
  "data": [
    { "profile_data": { "...": "name, headline, location, current role" } }
  ],
  "meta": {
    "count": 25,
    "limit": 25,
    "nextCursor": "opaque-signed-string"
  }
}

Source: response shape from the API reference, api.leadocean.io/openapi.json, September 2026. The last page has "nextCursor": null. The console line from the program reads 3 pages written, or fewer if the search ends sooner.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key. System.getenv returned null.Run echo $LEADOCEAN_API_KEY in the same shell. Copy the key again from the app.
429You went above the plan's rate limit: 5 a second on Free, 200 on Agency.Keep the sleep. The code already honors Retry-After.
402Free plan only: the 1,000 records are spent and do not reset. Paid plans slow down instead.Move to Solo at $97 a month or Agency at $497 a month. See pricing.
400 "Invalid cursor"The cursor was edited, truncated or reused with a different search.Pass meta.nextCursor back unchanged, with the same filters. Otherwise start over.
Empty data or a 400 on page 1A filter value is not an exact enum string.Check the allowed values with GET /v1/enums.
No cursor before you expectedThe search reached the 10,000 row depth cap (meta.depthCapped).Narrow the filters and run smaller searches.

FAQ

Does Java need an SDK for this?

No. java.net.http.HttpClient ships with the JDK since version 11. The API is plain JSON over HTTPS with one header.

How many records does a full walk spend?

One per person returned, so a 10,000 row walk spends 10,000. Counts, empty pages and 404s cost nothing. Size the search first and test with 3 pages.

Can I jump to page 50?

No. A cursor is a position in one walk, so you move forward one page at a time. To split the work, narrow the filters into several searches.

Should I use search or an export?

Search suits sampling, small lists and feeding enrichment. For a large list as a file, use POST /v1/exports. See the same recipe in Go, C# and TypeScript.

Page through your first people search with a free API key

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →