Blog
Migrating off Proxycurl: mapping profile and company enrichment to two endpoints
Proxycurl shut down on 4 July 2025 after LinkedIn sued. Move your profile and company enrichment onto two endpoints, with before and after code.
Proxycurl shut down on 4 July 2025, after LinkedIn filed suit against its parent company in January 2025. If your code called its Person Profile or Company Profile endpoints, those two calls have a direct replacement. The other nineteen Proxycurl endpoints do not, and this post is honest about which is which before you write a line.
What Triguna replaces, and what it does not
Proxycurl exposed twenty-one endpoints. Triguna has two, plus a balance check:
GET /v1/people/profile profile enrichment
GET /v1/companies/details company enrichment
GET /v1/credits credit balance
So this is a replacement for profile and company enrichment. It is not a replacement for search, employee listing, role lookup, jobs, or the school endpoint. There is no search or list path at all, which means enriching N entities is N calls, not one query that returns a page. If your Proxycurl usage was mostly the profile and company endpoints, this is a clean swap. If it leaned on search or listing, you need a different source for that part, and cutting it over to Triguna will quietly drop those workflows.
The endpoint mapping
| Proxycurl | Triguna | The catch |
|---|---|---|
Person Profile GET /proxycurl/api/v2/linkedin | GET /v1/people/profile | pass a slug in profile_id, not a full URL |
Company Profile GET /proxycurl/api/linkedin/company | GET /v1/companies/details | pass the URL slug in company_id, not the display name |
| Person, Company, or Role Search | none | no search or list path exists |
| Employee Listing | none | N entities is N direct calls |
| Jobs, School, and the rest | none | out of scope for an enrichment API |
Three things change in every request
The header, the parameter, and the base URL. Here is the person call in Node, before and after.
Before, on Proxycurl:
const res = await fetch(
'https://nubela.co/proxycurl/api/v2/linkedin?url=' +
encodeURIComponent('https://www.linkedin.com/in/williamhgates'),
{ headers: { Authorization: `Bearer ${process.env.PROXYCURL_KEY}` } },
);
const profile = await res.json();
After, on Triguna:
const res = await fetch(
'https://api.triguna.ai/v1/people/profile?profile_id=williamhgates',
{ headers: { ApiKey: process.env.TRIGUNA_API_KEY } },
);
const profile = await res.json();
The auth header is ApiKey, not Authorization: Bearer. The identifier is a
slug in profile_id, not a full linkedin.com URL in url. That is the whole
swap for a profile lookup. The Profile API page shows
the response shape.
The company call in Python, before and after:
import os, requests
# Before, on Proxycurl
res = requests.get(
'https://nubela.co/proxycurl/api/linkedin/company',
headers={'Authorization': f"Bearer {os.environ['PROXYCURL_KEY']}"},
params={'url': 'https://www.linkedin.com/company/microsoft'},
)
company = res.json()
import os, requests
# After, on Triguna
res = requests.get(
'https://api.triguna.ai/v1/companies/details',
headers={'ApiKey': os.environ['TRIGUNA_API_KEY']},
params={'company_id': 'microsoft'},
)
company = res.json()
company_id takes the URL slug, the path segment after /company/, so
microsoft works and Microsoft Corporation returns a 404. The
Company API page covers the fields, and the
company records guide covers the two normalizations
that catch people: platform_url is the page on the source platform while
company_url is the company’s own site, and it is specialities, not
specialties.
Provenance moves from the body to the headers
If your Proxycurl code read metadata out of the JSON body, that read has to move. Triguna keeps provenance in the response headers and never in the body, so a stored copy carries where it came from and when:
X-Data-Source: ...
X-Fetched-At: 2026-08-16T09:12:44Z
X-Data-Age-Seconds: 0
X-Credits-Charged: 1
X-RateLimit-Remaining: 9
There is no source field inside the JSON to parse. Record X-Data-Source and
X-Fetched-At alongside the row. The full header list is in the
caching reference.
Billing and retries differ enough to matter
Read this before you point production traffic at it, because the failure economics are not the same.
- One credit per answered lookup, at $0.005 per credit. A cache hit costs one
credit: same data, delivered faster.
use_cachedefaults totrue. - A 404 costs one credit. Searching for an identifier that has no record is a real retrieval, so a missing profile bills the same as a found one. Never retry a 404, because a retried 404 costs a credit every time.
- Free statuses, charged nothing: 400, 401, 402, 429, 502, 503. Retry only 429, 502, and 503, with backoff. The published rate limit is 10 requests per second.
The pricing page has the per-status table, and the error reference has the bodies.
The migration checklist
- Swap the auth header from
Authorization: BearertoApiKey. - Replace the full
linkedin.comURL with the slug:profile_idfor people,company_idfor companies. - Change the base URL and the two endpoint paths per the table above.
- Move any provenance read from the JSON body to the
X-headers. - Delete retry logic on 404. It now costs a credit each attempt.
- Find a different source for search, employee listing, jobs, and school. There is no equivalent here.
- Store
entity_urnandcompany_id, not the editable public slug, so your joins survive a rename.
If you are wiring the first call into a signup flow rather than a backfill, the enrich on signup guide has the pattern that keeps a slow lookup off the critical path.
FAQ
Does Triguna replace Proxycurl’s search endpoints? No. It is two enrichment endpoints plus a credit balance check. There is no search, employee listing, jobs, or school lookup, and no bulk path.
Is there an SDK to drop in? No SDK, which matches Proxycurl’s plain REST. A
GET with an ApiKey header is the entire client. Agents can read
api.triguna.ai/llms.txt and the OpenAPI spec directly.
Do cache hits cost a credit? Yes. A cache hit is the same data delivered
faster, and it bills one credit. During an outage a stored copy can come back
marked stale_fallback instead of erroring.
Is there a bulk endpoint? No. N entities is N calls at up to 10 requests per second. Pace your backfill accordingly.
How do I get a key? Sign up for five credits, no card. The people who build this were Proxycurl customers before they were operators of an enrichment API, which is why the surface stays small enough to hold in your head.
Next
- Profile API: the
GET /v1/people/profileresponse, field by field. - Company API:
GET /v1/companies/detailsand the slug trap. - When your enrichment provider goes dark: why you store raw payloads before a vendor disappears, not after.
- Enrich on signup: put the first call somewhere it will not block a user.
- Pricing: what each status code costs, including the 404.