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

API tutorial

How to Enrich a List of LinkedIn URLs (in Java)

One file of LinkedIn URLs 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 can enrich a list of LinkedIn URLs in Java with one HTTP call per URL to POST /v1/people/enrich. The code below reads a text file, retries on 429, skips misses and writes one JSON line per person.

TL;DR

  • Java 17 and the built-in java.net.http.HttpClient. No Maven dependencies.
  • About 15 minutes to set up, plus at least 0.2 seconds per URL at a steady pace.
  • It spends one LeadOcean record per URL we hold. A URL we hold nothing for returns 404 and costs nothing. The Free plan has 1,000 records for life.

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.
  • A file named urls.txt with one LinkedIn profile URL per line.
bash
export LEADOCEAN_API_KEY="your-key-here"
printf '%s\n' \
  "https://www.linkedin.com/in/jane-doe-example" \
  "https://www.linkedin.com/in/john-roe-example" > urls.txt

Step 1: Understand the call

Enrich takes exactly one identifier: linkedin_url, email, phone or person_id. Send it in a JSON body to https://api.leadocean.io/v1/people/enrich with the header x-api-key.

Set reveal_email to true to get the email and phone we hold at no extra record. Leave it out and you get the profile and career history only.

bash
curl -s -X POST https://api.leadocean.io/v1/people/enrich \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"linkedin_url":"https://www.linkedin.com/in/jane-doe-example","reveal_email":true}'

Run this once before any Java. A 200 means your key and URL format are right.

Step 2: Write Enrich.java

Save this as Enrich.java. It reads the key from the environment, never from source.

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

public class Enrich {
  static final HttpClient HTTP = HttpClient.newHttpClient();
  static final String KEY = System.getenv("LEADOCEAN_API_KEY");

  static String enrich(String linkedinUrl) throws Exception {
    String body = "{\"linkedin_url\":\"" + linkedinUrl.replace("\"", "") + "\",\"reveal_email\":true}";
    HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.leadocean.io/v1/people/enrich"))
        .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());
      int status = res.statusCode();
      if (status == 200) return res.body().replaceAll("\\s*\\n\\s*", " ").strip();
      if (status == 404) return null;                      // nobody held, no record spent
      if (status == 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(status + " " + res.body());
    }
    throw new IllegalStateException("still rate limited: " + linkedinUrl);
  }

  public static void main(String[] args) throws Exception {
    List<String> urls = Files.readAllLines(Path.of("urls.txt")).stream()
        .map(String::strip).filter(u -> !u.isEmpty()).distinct().toList();
    List<String> out = new ArrayList<>();
    int misses = 0;
    for (String u : urls) {
      String json = enrich(u);
      Thread.sleep(200);                                   // 5 a second, the Free limit
      if (json == null) { misses++; continue; }
      out.add(json);
    }
    Files.write(Path.of("enriched.jsonl"), out);
    System.out.printf("%d enriched, %d not found%n", out.size(), misses);
  }
}

Run it with the single-file launcher:

bash
java Enrich.java

Step 3: Handle rate limits and paging

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 so you rarely see a 429. On Agency, drop it to 5 milliseconds or run several threads. Each thread shares the same HttpClient, which is safe.

Paging here means chunking your own list. Enrich has no cursor, because each call returns one person. For a long file, split urls.txt into files of a few thousand lines, run one at a time and keep each enriched.jsonl. A crash then costs you one chunk, not the whole run.

The distinct() call removes duplicate URLs so you never pay twice for the same person.

Step 4: Write results where you can use them

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

bash
jq -r '.data.contact_data | [.email_status, .has_phone] | @csv' enriched.jsonl

Add Jackson or Gson later if you want typed objects. For a CSV, pick the fields you need from data.contact_data.contact_emails[] and data.profile_data. Check email_status before you send anything. We do not refund or credit back bounced emails.

Since 17 October 2026 the response also carries resume_data: past roles, education, certifications and awards, each when we hold them. Past employers have no company_id yet.

What you get

This is the response shape from the API reference, with placeholder values. Fields appear only when we hold them.

json
{
  "success": true,
  "data": {
    "profile_data": { "profile_full_name": "Jane Doe", "profile_headline": "VP Sales at Acme" },
    "contact_data": {
      "contact_emails": [{ "email": "jane.doe@acme.com", "type": "work", "status": "verified" }],
      "contact_phones": [{ "phone": "+1 415 555 0133", "type": "mobile" }],
      "contact_current_experiences": [{ "job_title": "VP Sales", "company_name": "Acme" }],
      "has_email": true,
      "has_phone": true,
      "email_status": "verified"
    },
    "resume_data": { "experiences": [], "educations": [] }
  },
  "meta": { "key_hash": "...", "fetched_at": "2026-10-01" }
}

Source: response shape from the API reference, api.leadocean.io/openapi.json, September 2026. Email and phone arrays appear only with reveal_email set to true.

The program ends by printing one line in the form 2 enriched, 0 not found, with the counts for your list.

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 or lower the thread count. The code already honors Retry-After.
402You spent your records. Free is 1,000 for life.Move to Solo at $97 a month or Agency at $497 a month. See pricing.
404We hold nothing for that person.Skip it. The code counts it as a miss, and it costs nothing.
400 or a malformed-body errorMore than one identifier, or a broken URL string.Send exactly one of linkedin_url, email, phone or person_id.
still rate limited after 5 triesMany threads sharing one key.Reduce parallelism or ask for a higher plan.

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 list of 1,000 URLs spend?

At most 1,000, one per URL we hold. URLs we hold nothing for return 404 and cost nothing. Duplicates are removed in the code before the first call.

Can I send a name and company instead of a URL?

Not directly. Enrich takes one of linkedin_url, email, phone or person_id. Search by domain with /v1/people/search first, then enrich the person_id.

Is there a batch endpoint?

Enrich is one person per call. For very large lists, run threads within your plan's rate limit. See the same recipe in Go, C# and TypeScript, and more recipes in the blog.

Enrich your first LinkedIn URLs with a free API key

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

Get your free API key →