You can enrich a list of LinkedIn URLs in Go with one standard-library file: read the URLs, call POST /v1/people/enrich for each, wait out any 429, and write the answers to a CSV. This guide gives you that file and explains each part.
TL;DR
- One Go file, 46 lines, no third-party packages. It reads
urls.txtand writespeople.csv. - Time: about 10 minutes to set up. At 4 requests a second, 1,000 URLs take at least 4 minutes.
- Cost: 1 LeadOcean record per URL we hold a match for. A miss is a 404 and is free, so the Free plan's 1,000 records cover up to 1,000 matches.
Prerequisites
- Go 1.21 or newer.
- A LeadOcean API key, exported as
$LEADOCEAN_API_KEY. The Free plan gives you one with 1,000 records for life, no card. - A
urls.txtfile with one LinkedIn profile URL per line.
Step 1: Get a key and prepare the list
Sign up at app.leadocean.io, create a key and put it in your shell. Never write the key into the source file.
export LEADOCEAN_API_KEY="your-key-here"
printf '%s\n' \
"https://www.linkedin.com/in/jane-doe-acme" \
"https://www.linkedin.com/in/john-roe-acme" > urls.txtThe enrich endpoint takes exactly one identifier per call: linkedin_url, email, phone or person_id. Enrich does not page. You make one call per URL, so "paging" here means walking your file line by line.
Step 2: Write the Go program
Save this as main.go. Requests go to https://api.leadocean.io with the x-api-key header.
package main
import (
"bufio"; "bytes"; "encoding/csv"; "encoding/json"
"io"; "net/http"; "os"; "strconv"; "strings"; "time"
)
func enrich(key, url string) (int, string) {
body, _ := json.Marshal(map[string]any{"linkedin_url": url, "reveal_email": true})
for try := 0; try < 5; try++ {
req, _ := http.NewRequest("POST", "https://api.leadocean.io/v1/people/enrich", bytes.NewReader(body))
req.Header.Set("x-api-key", key)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
time.Sleep(time.Second)
continue
}
data, _ := io.ReadAll(res.Body)
res.Body.Close()
if res.StatusCode == 429 {
wait, _ := strconv.Atoi(res.Header.Get("Retry-After"))
time.Sleep(time.Duration(max(wait, 1)) * time.Second)
continue
}
return res.StatusCode, string(data)
}
return 0, "gave up after 5 tries"
}
func main() {
key := os.Getenv("LEADOCEAN_API_KEY")
in, _ := os.Open("urls.txt")
out, _ := os.Create("people.csv")
w := csv.NewWriter(out)
defer w.Flush()
w.Write([]string{"linkedin_url", "status", "json"})
sc := bufio.NewScanner(in)
for sc.Scan() {
if u := strings.TrimSpace(sc.Text()); u != "" {
status, data := enrich(key, u)
w.Write([]string{u, strconv.Itoa(status), data})
time.Sleep(250 * time.Millisecond)
}
}
}Three choices in that file matter:
reveal_email: truereturns the contact points on the record. It adds no extra record.- The 250 ms pause keeps you at 4 requests a second, under the Free plan's limit of 5.
- On a 429 the code reads
Retry-After, sleeps that many seconds, and tries again, up to 5 times. See API rate limits and 429 errors.
Step 3: Run it
go run main.go
head -3 people.csvThe status column tells you what happened per URL: 200 is a match, 404 is nobody found. Filter on it before you load rows anywhere else.
Step 4: Parse the fields you need
The CSV stores the raw JSON so you lose nothing. When you know which fields you want, unmarshal data into a struct. The current role is in contact_current_experiences[] and phones are in contact_phones[].phone. Since October 2026 the same call also returns career history in resume_data: past roles, education, certifications and awards.
Past employers carry no company_id yet. If you need a company record, call POST /v1/companies/enrich with the domain.
What you get
The response shape from the API reference, with placeholder values. This is not a real person.
{
"success": true,
"data": {
"profile_data": { "profile_full_name": "Jane Doe", "profile_headline": "VP Sales at Acme" },
"contact_data": {
"has_email": true,
"email_status": "verified",
"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" }]
},
"resume_data": { "experiences": [], "educations": [], "certifications": [] }
},
"meta": { "key_hash": "...", "fetched_at": "2026-10-01" }
}Source: response shape from the API reference (openapi.json, October 2026), with field names from the enrich docs. Each record also carries a fetched_at date, and the dataset refreshes monthly.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| 401 | Missing or wrong x-api-key, or $LEADOCEAN_API_KEY is empty in this shell | Re-export the key and check with echo ${#LEADOCEAN_API_KEY} |
| 429 | You sent faster than the plan allows: 5 a second on Free, 200 on Agency | Raise the sleep, or rely on the Retry-After retry in the code |
| 402 | The records are spent. Free is 1,000 for life | Stop the run and upgrade. Paid plans slow at their pace instead of returning 402 |
| 404 | No person matched that URL. It is free | Keep the row, mark it unmatched, move on |
FAQ
Can I run the calls in parallel?
Yes, with goroutines and a shared ticker. Keep the total under your plan's per-account limit: 5 requests a second on Free, 50 on Solo, 200 on Agency. A single sequential loop is simpler, and on Free it is all the speed you can use.
Does a URL with no match use a record?
No. A person we hold nothing for is a 404 and costs nothing. Only a match returns a record.
Should I use GET or POST?
Both work on /v1/people/enrich. POST with a JSON body avoids URL-encoding the LinkedIn address.
What if I have tens of thousands of URLs?
Run the same loop in batches and write each batch to its own file. Solo allows 10k enrichments every 5 hours and Agency 1M a day. Paid plans have a fair-use pace instead of credits, and reaching it slows the key rather than stopping it.
Is there a bulk option?
Not for a list of URLs. Enrich is one person per call. POST /v1/exports exports an audience built from search filters, up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free; it starts on Solo at $97 a month.
See the same recipe in C#, TypeScript and Rust.
Enrich your first 1,000 LinkedIn URLs free
Free to start. No credit card. 1,000 records to spend whenever you like.
Get your free API key →