Rynora API

LinkedIn people, organizations, jobs and contact details, as JSON. One key, one response envelope, and a credit cost printed on every reply.

Join the waitlist for access, or read on — everything below is public.

What this API offers

Live API - Person9fetch a person from LinkedIn now, or a list of them in one batch: profile, employment history, endorsements, interests, similar people
Live API - Organization10fetch a company now, or a list of them in one batch: profile, headcount, similar and affiliated pages, its posts and job openings
Live API - Activity5posts, reactions and replies
Live API - Opportunity5job postings, the hiring team behind one, and jobs a person has posted
Live API - Discover3search LinkedIn itself for people, companies and jobs not yet collected
Live API - Contact5work email and phone: find from a name and domain, or look up who an address or number belongs to
Watchlist9track people and companies over time: poll them on a schedule and get a structured diff of what changed - job moves, headline edits, new posts, headcount - read by API or emailed to subscribers. This is the only family that answers what changed rather than what is true now

Overview

Every endpoint goes to LinkedIn the first time it is asked about a subject, and answers from what that fetch stored every time after.

CallBehaviour
first callGoes to LinkedIn now. Seconds to minutes: a browser walks the pages and stores what it finds.
afterwardsAnswers from the stored copy. Milliseconds, until the record is a month old. The views over one subject — a person's employment history, endorsements, interests — share the fetch its enrich made.
Have a list rather than a name? batch/enrich takes up to a thousand at once and works through them behind the request.

Authentication

Every endpoint except the unsubscribe links needs a key, sent as a header.

X-API-Key: lsk_...
Getting one

Accounts are created by invitation. Accept yours at https://rynora.dev/app, sign in, and create a key from the Keys panel. A key is shown once and stored only as a hash, so it cannot be recovered later — keep it somewhere safe. The same panel shows your credit balance and spending history. Without an invitation, ask for one; there is no self-service sign-up.

Responses

Everything under /api uses one envelope. Read success, then data.

{
  "success": true,
  "statusCode": 200,
  "message": "OK",
  "errors": null,
  "data": { ... }
}
The statusCode in the body is the meaningful one. A well-formed request that simply found nothing returns HTTP 200 with success: false and a 404 in the body, because the request itself was fine. Check the body, not the transport.

The Watchlist endpoints are the exception: they return bare JSON without this wrapper.

Credits and limits

Every reply carries what it cost and what is left.

X-Credits-Charged: 1
X-Credits-Remaining: 899
CostApplies to
1almost everything
2/discover/*, and subscribing an address to the watchlist
0any DELETE — stopping should never cost anything; and submitting, polling or listing batch jobs, since each subject a job resolves is charged 1 as it lands
10each watchlist entity, charged when it is actually polled rather than when you add it

A new account starts with 100 free credits. Running out returns HTTP 402. Rate limits are 60 requests a minute with a key; exceeding that returns HTTP 429 with a Retry-After header.

A not-found is free. A lookup that runs and finds nobody - a person, company, skill, institution or job not in the graph (success: false with a 10xx/12xx code) - is refunded, and the response carries X-Credits-Refunded: true with X-Credits-Charged: 0, so a backfill pays only for the subjects that resolve. A malformed request (a bad id, a missing filter) is not a not-found and is not refunded; every call is metered against the rate limit whether or not its credit is returned.

For coding agents

A plain-text version of this reference, structured for an LLM to read directly rather than scrape out of markup.

https://api.rynora.dev/llms.txt

It carries the same endpoints, parameters and response shapes as this page, plus the machine-readable schema at /openapi.json. Point a coding agent at the first URL and it can call the API without further explanation.

Live API - Person

One person, fetched from LinkedIn. `enrich` does the fetching; the other five are views over what it returned, so call `enrich` first and the rest are free. `batch/enrich` is the same fetch over a list of up to 1000, worked through behind the request.

POST/api/v2/person/batch/enrich0 credits

Submit a batch person-enrich job

Parameters and response

Enrich a list of people in the background.

The batch form of `enrich`: accepts entityIds and handles, returns at once with a job token, and resolves them one at a time behind the request. Poll the GET on this path to stream results as they land. One credit per profile that resolves, charged as the worker gets to it; a not-found is free; submitting and polling are free.

JSON body
FieldTypeMeaning
idsarray optionalStable identifiers: an entityId (ACoAA...) or object id for a person, a numeric organization id or object id for an organization.
handlesarray optionalPublic handles: the slug after /in/ for a person, after /company/ for an organization.
Request
  curl -X POST 'https://api.rynora.dev/api/v2/person/batch/enrich' \
    -H 'X-API-Key: $RYNORA_KEY' \
    -H 'Content-Type: application/json' \
    -d '{"handles": ["jane-doe-example", "john-doe-example"]}'
GET/api/v2/person/batch/enrich0 credits

Track a batch job and stream its results

Parameters and response

Job progress plus the next page of resolved profiles.

Free to poll: each profile was charged as it resolved, so read as often as the rate limit allows. `credits_charged` reports the running total.

Query parameters
NameMeaning
job_idrequiredThe token returned by the POST
afteroptionalHow many results you have already taken. Start at 0, then pass back the next_cursor from your previous poll to receive only what is new.
Request
  curl 'https://api.rynora.dev/api/v2/person/batch/enrich?job_id=job_the-token-from-the-POST' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/person/batch/jobs0 credits

List your batch jobs

Parameters and response

Every person batch job this key's account has submitted, newest first, with its token - for when the 202 was not kept. Free. Jobs are deleted six hours after submission, so this is a recent-history view, not an archive.

Query parameters
NameMeaning
statusoptionalOnly jobs in this state. Omit for all.
Request
  curl 'https://api.rynora.dev/api/v2/person/batch/jobs' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/person/employment-history1 credit

Get full employment history

Parameters and response

Every position with its dates, plus the tenure arithmetic a caller would otherwise have to redo from prose date strings.

Query parameters
NameMeaning
entityIdrequiredThe person's opaque LinkedIn token (ACoAA...). Get one from /person/resolve-handle. Preferred over a handle because it survives the person renaming their profile.
Request
  curl 'https://api.rynora.dev/api/v2/person/employment-history?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "entityId": "string",
  "handle": "string",
  "id": "string",
  "linkedin_url": "string",
  "positions": [
    {
      "company": "string",
      "company_url": "string",
      "description": "string",
      "duration": "string",
      "end_date": "string",
      "end_year": "string|null",
      "is_current": "boolean",
      "location": "string",
      "start_date": "string",
      "start_year": "integer",
      "title": "string"
    }
  ],
  "summary": {
    "current_positions": "integer",
    "earliest_year": "integer",
    "employer_count": "integer",
    "position_count": "integer"
  }
}
GET/api/v2/person/endorsements1 credit

Get recommendations / endorsements

Parameters and response

Recommendations written for this person, with their authors.

Query parameters
NameMeaning
entityIdrequiredThe person's opaque LinkedIn token (ACoAA...). Get one from /person/resolve-handle. Preferred over a handle because it survives the person renaming their profile.
Request
  curl 'https://api.rynora.dev/api/v2/person/endorsements?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "count": "integer",
  "entityId": "string",
  "handle": "string",
  "id": "string",
  "linkedin_url": "string",
  "recommendations": [
    "<empty>"
  ]
}
GET/api/v2/person/enrich1 credit

Enrich a person (full profile)

Parameters and response

The complete profile for one person, fetched live.

Query parameters
NameMeaning
entityIdone of theseOpaque ACoAA... token Provide exactly one of the identifiers marked “one of these”.
handleone of thesePublic profile slug Provide exactly one of the identifiers marked “one of these”.
refreshoptionalFetch from LinkedIn now. Set false to accept the stored copy, which answers in milliseconds instead of minutes.
Request
  curl 'https://api.rynora.dev/api/v2/person/enrich?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "certifications": [
    "<empty>"
  ],
  "courses": [
    "<empty>"
  ],
  "education": [
    {
      "degree": "string",
      "description": "string|null",
      "end_date": "string",
      "field_of_study": "string|null",
      "institution": {
        "id": "string|null",
        "linkedin_url": "string|null",
        "name": "string"
      },
      "start_date": "string"
    }
  ],
  "entityId": "string",
  "handle": "string",
  "headline": "string",
  "honors": [
    "<empty>"
  ],
  "id": "string",
  "languages": [
    "string"
  ],
  "last_updated": "string",
  "linkedin_url": "string",
  "location": "string|null",
  "name": "string",
  "open_to_work": "boolean",
  "positions": [
    {
      "description": "string",
      "duration": "string|null",
      "end_date": "string",
      "is_current": "boolean",
      "location": "string|null",
      "organization": {
        "id": "string|null",
        "linkedin_url": "string|null",
        "name": "string",
        "slug": "string|null"
      },
      "start_date": "string",
      "title": "string"
    }
  ],
  "profile_picture_url": "string",
  "projects": [
    "<empty>"
  ],
  "publications": [
    "<empty>"
  ],
  "recommendation_count": "integer",
  "skills": [
    "string"
  ],
  "summary": "string",
  "volunteering": [
    "<empty>"
  ]
}
GET/api/v2/person/interests1 credit

Get a person's interests

Parameters and response

What the person follows, grouped by the kind of thing it is.

Grouped rather than returned flat because the categories answer different questions - the companies somebody follows and the newsletters they read are not the same signal.

Query parameters
NameMeaning
entityIdrequiredThe person's opaque LinkedIn token (ACoAA...). Get one from /person/resolve-handle. Preferred over a handle because it survives the person renaming their profile.
Request
  curl 'https://api.rynora.dev/api/v2/person/interests?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "categories": [
    "string"
  ],
  "count": "integer",
  "entityId": "string",
  "handle": "string",
  "id": "string",
  "interests": {
    "companies": [
      {
        "linkedin_url": "string|null",
        "name": "string"
      }
    ]
  },
  "linkedin_url": "string"
}
GET/api/v2/person/lookalikes1 credit

Get similar people

Parameters and response

Profiles LinkedIn shows alongside this one.

Query parameters
NameMeaning
entityIdrequiredThe person's opaque LinkedIn token (ACoAA...). Get one from /person/resolve-handle. Preferred over a handle because it survives the person renaming their profile.
Request
  curl 'https://api.rynora.dev/api/v2/person/lookalikes?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "count": "integer",
  "entityId": "string",
  "handle": "string",
  "id": "string",
  "linkedin_url": "string",
  "results": [
    {
      "degree": "string",
      "headline": "string",
      "linkedin_url": "string",
      "name": "string"
    }
  ]
}
GET/api/v2/person/resolve-handle1 credit

Resolve a handle to an entityId

Parameters and response

Turn a public handle into the stable entityId.

The entityId is worth the round trip because it is the one identifier that survives the person renaming their profile.

Query parameters
NameMeaning
handlerequiredThe slug from a profile URL
Request
  curl 'https://api.rynora.dev/api/v2/person/resolve-handle?handle=jane-doe-example' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "entityId": "string",
  "handle": "string",
  "id": "string",
  "linkedin_url": "string"
}

Live API - Organization

One company, fetched from LinkedIn. Same shape as the Person family: `enrich` fetches, the rest read, and `batch/enrich` takes a list.

GET/api/v2/organization/activities1 credit

Get an organization's activity (posts)

Parameters and response

Paging is free after the first fetch: the whole feed is stored and paged locally, as it is for a person.

Query parameters
NameMeaning
idrequiredThe organization's numeric LinkedIn page id, e.g. 1000000002. Get one from /organization/resolve-slug. This is the same id a person's positions carry, so it can be passed straight across.
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
countoptionalHow many results to return in this page.
Request
  curl 'https://api.rynora.dev/api/v2/organization/activities?id=1000000002' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "count": "integer",
  "hasMore": "boolean",
  "id": "string",
  "linkedin_url": "string",
  "name": "string",
  "nextCursor": "string",
  "objectId": "string",
  "posts": [
    {
      "article_url": "string|null",
      "comments_count": "integer",
      "image_urls": [
        "<empty>"
      ],
      "linkedin_url": "string",
      "posted_date": "string",
      "reactions_count": "integer",
      "reposts_count": "integer",
      "text": "string",
      "urn": "string",
      "video_url": "string|null"
    }
  ],
  "slug": "string",
  "start": "integer",
  "total": "integer"
}
GET/api/v2/organization/affiliated1 credit

Get affiliated pages

Parameters and response

Subsidiaries, regional pages and showcase pages.

Most companies have none, so an empty list is the ordinary answer - the two kinds are reported separately because LinkedIn treats them as different things.

Query parameters
NameMeaning
idrequiredThe organization's numeric LinkedIn page id, e.g. 1000000002. Get one from /organization/resolve-slug. This is the same id a person's positions carry, so it can be passed straight across.
Request
  curl 'https://api.rynora.dev/api/v2/organization/affiliated?id=1000000002' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "affiliated_companies": [
    "<empty>"
  ],
  "count": "integer",
  "id": "string",
  "linkedin_url": "string",
  "name": "string",
  "objectId": "string",
  "showcase_pages": [
    "<empty>"
  ],
  "slug": "string"
}
POST/api/v2/organization/batch/enrich0 credits

Submit a batch organization-enrich job

Parameters and response

Enrich a list of organizations in the background.

The batch form of `enrich`: accepts numeric ids and slugs, returns at once with a job token, and resolves them one at a time behind the request. One credit per organization that resolves; a not-found is free; submitting and polling are free.

JSON body
FieldTypeMeaning
idsarray optionalStable identifiers: an entityId (ACoAA...) or object id for a person, a numeric organization id or object id for an organization.
handlesarray optionalPublic handles: the slug after /in/ for a person, after /company/ for an organization.
Request
  curl -X POST 'https://api.rynora.dev/api/v2/organization/batch/enrich' \
    -H 'X-API-Key: $RYNORA_KEY' \
    -H 'Content-Type: application/json' \
    -d '{"handles": ["example-corp", "example-inc"]}'
GET/api/v2/organization/batch/enrich0 credits

Track a batch job and stream its results

Parameters and response

Job progress plus the next page of resolved organizations.

A token from the person batch endpoint returns 404 here: it is a different job, and answering it in this shape would be worse than not answering.

Query parameters
NameMeaning
job_idrequiredThe token returned by the POST
afteroptionalHow many results you have already taken. Start at 0, then pass back the next_cursor from your previous poll to receive only what is new.
Request
  curl 'https://api.rynora.dev/api/v2/organization/batch/enrich?job_id=job_the-token-from-the-POST' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/organization/batch/jobs0 credits

List your batch jobs

Parameters and response

Every organization batch job this key's account has submitted, newest first, with its token - for when the 202 was not kept. Free. Jobs are deleted six hours after submission, so this is a recent-history view, not an archive.

Query parameters
NameMeaning
statusoptionalOnly jobs in this state. Omit for all.
Request
  curl 'https://api.rynora.dev/api/v2/organization/batch/jobs' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/organization/enrich1 credit

Enrich an organization (full profile)

Parameters and response

The complete company record for a numeric id.

Query parameters
NameMeaning
idrequiredThe numeric organization id
Request
  curl 'https://api.rynora.dev/api/v2/organization/enrich?id=1000000002' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "affiliated_companies": [
    "<empty>"
  ],
  "company_size": "string",
  "cover_image_url": "string|null",
  "crunchbase_url": "string|null",
  "description": "string",
  "follower_count": "string|null",
  "founded": "string",
  "headcount": "integer",
  "headquarters": {
    "city": "string",
    "country": "string|null",
    "country_code": "string|null",
    "line": "string",
    "region": "string"
  },
  "id": "string",
  "industries": [
    "string"
  ],
  "last_updated": "string",
  "linkedin_url": "string",
  "logo_url": "string",
  "name": "string",
  "objectId": "string",
  "phone": "string|null",
  "showcase_pages": [
    "<empty>"
  ],
  "slug": "string",
  "source": "string",
  "specialities": [
    "<empty>"
  ],
  "tagline": "string|null",
  "type": "string|null",
  "website": "string"
}
GET/api/v2/organization/headcount1 credit

Get organization headcount data

Parameters and response

Employee count and the size bucket LinkedIn publishes.

The per-function distribution the contract mentions is not here: LinkedIn puts it behind Sales Navigator's insights, not on the public page, so reporting it would mean inventing it.

Query parameters
NameMeaning
idrequiredThe organization's numeric LinkedIn page id, e.g. 1000000002. Get one from /organization/resolve-slug. This is the same id a person's positions carry, so it can be passed straight across.
Request
  curl 'https://api.rynora.dev/api/v2/organization/headcount?id=1000000002' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "distribution": "string|null",
  "distribution_note": "string",
  "employee_count": "integer",
  "id": "string",
  "last_updated": "string",
  "linkedin_url": "string",
  "name": "string",
  "objectId": "string",
  "size_range": "string",
  "slug": "string"
}
GET/api/v2/organization/opportunities1 credit

Get an organization's opportunities (jobs)

Parameters and response

Open postings across one or more organizations.

Query parameters
NameMeaning
organizationEntityIdsrequiredComma-separated ids
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
countoptionalHow many results to return in this page.
refreshoptional
Request
  curl 'https://api.rynora.dev/api/v2/organization/opportunities?organizationEntityIds=value' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/organization/resolve-slug1 credit

Resolve a slug to an organization id

Parameters and response

Turn a public slug into the numeric id the other endpoints want.

Costs a fetch the first time and nothing afterwards.

Query parameters
NameMeaning
slugrequiredThe segment after /company/
Request
  curl 'https://api.rynora.dev/api/v2/organization/resolve-slug?slug=example-corp' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "id": "string",
  "linkedin_url": "string",
  "name": "string",
  "objectId": "string",
  "slug": "string"
}
GET/api/v2/organization/similar1 credit

Get similar organizations

Parameters and response

Companies LinkedIn shows beside this one - its peer rail.

Query parameters
NameMeaning
idrequiredThe organization's numeric LinkedIn page id, e.g. 1000000002. Get one from /organization/resolve-slug. This is the same id a person's positions carry, so it can be passed straight across.
Request
  curl 'https://api.rynora.dev/api/v2/organization/similar?id=1000000002' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "count": "integer",
  "id": "string",
  "linkedin_url": "string",
  "name": "string",
  "objectId": "string",
  "results": [
    {
      "followers": "string",
      "linkedin_url": "string",
      "name": "string"
    }
  ],
  "slug": "string"
}

Live API - Activity

Posts, reactions and replies.

GET/api/v2/activity/details1 credit

Get details of a single activity

Parameters and response

Full content and engagement for one post.

Query parameters
NameMeaning
entityIdrequiredThe activity urn
refreshoptional
Request
  curl 'https://api.rynora.dev/api/v2/activity/details?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/activity/feed1 credit

Get a person's activity feed

Parameters and response

Recent posts and shares by one person.

Query parameters
NameMeaning
entityIdrequiredThe person's ACoAA... token
cursoroptionalOpaque pagination token. Omit for the first page, then pass the next_cursor from the previous response. Treat it as a black box: it encodes a position, not a page number.
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
countoptionalHow many results to return in this page.
Request
  curl 'https://api.rynora.dev/api/v2/activity/feed?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "count": "integer",
  "entityId": "string",
  "hasMore": "boolean",
  "nextCursor": "string",
  "posts": [
    {
      "article_url": "string|null",
      "comments_count": "integer",
      "image_urls": [
        "<empty>"
      ],
      "linkedin_url": "string",
      "posted_date": "string",
      "reactions_count": "integer",
      "reposts_count": "string|null",
      "text": "string",
      "urn": "string",
      "video_url": "string|null"
    }
  ],
  "start": "integer",
  "total": "integer"
}
GET/api/v2/activity/reactions1 credit

Get reactions on an activity

Parameters and response

The people behind a post's reaction count.

`start` moves in tens because that is how LinkedIn pages the reactions modal; a value in between would silently land on a different page than the caller asked for, so it is rejected instead.

Query parameters
NameMeaning
entityIdrequiredThe activity urn
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
refreshoptional
Request
  curl 'https://api.rynora.dev/api/v2/activity/reactions?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/activity/replies1 credit

Get replies (comments) on an activity

Parameters and response

The conversation under a post.

LinkedIn renders comments in its own relevance order and hides the rest behind "Load more comments", so `date_posted` reverses what was rendered rather than re-sorting by a timestamp we do not reliably have - the cards carry "2w", not a date.

Query parameters
NameMeaning
entityIdrequiredThe activity urn
sortByoptionalResult order. relevance is LinkedIn's own ranking; date_posted puts the newest first.
countoptionalHow many results to return in this page.
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
refreshoptional
Request
  curl 'https://api.rynora.dev/api/v2/activity/replies?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/activity/replies/by-author1 credit

Get replies written by a person

Parameters and response

Comments this person wrote, across LinkedIn - not the comments on one of their posts.

Query parameters
NameMeaning
entityIdrequiredThe person's ACoAA... token
cursoroptionalOpaque pagination token. Omit for the first page, then pass the next_cursor from the previous response. Treat it as a black box: it encodes a position, not a page number.
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
countoptionalHow many results to return in this page.
refreshoptional
Request
  curl 'https://api.rynora.dev/api/v2/activity/replies/by-author?entityId=ACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "count": "integer",
  "entityId": "string",
  "hasMore": "boolean",
  "nextCursor": "string",
  "replies": [
    {
      "author_headline": "string",
      "author_name": "string",
      "author_url": "string",
      "post_author": "string|null",
      "post_urn": "string",
      "posted": "string",
      "reactions_count": "string|null",
      "text": "string",
      "urn": "string|null"
    }
  ],
  "source": "string",
  "start": "integer",
  "total": "integer"
}

Live API - Opportunity

Job postings, the teams hiring for them, and who has posted them.

GET/api/v2/opportunity/by-person1 credit

Get opportunities posted by a person

Parameters and response

Postings where this person is on the hiring team.

A reverse lookup over jobs already fetched, because LinkedIn publishes no "jobs posted by X" page. So this answers "which postings we hold name this person", which grows with the corpus rather than describing everything they have ever posted - the same shape as the employees endpoint.

Query parameters
NameMeaning
personEntityIdrequiredThe poster's ACoAA... token
countoptionalHow many results to return in this page.
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
Request
  curl 'https://api.rynora.dev/api/v2/opportunity/by-person?personEntityId=value' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/opportunity/details1 credit

Get opportunity (job) details

Parameters and response

The full posting: title, description, company, location, employment type and apply metadata.

Query parameters
NameMeaning
opportunityEntityIdrequiredThe numeric job id
Request
  curl 'https://api.rynora.dev/api/v2/opportunity/details?opportunityEntityId=value' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/opportunity/hiring-team1 credit

Get the hiring team for an opportunity

Parameters and response

The recruiters and hiring managers attached to a posting.

Query parameters
NameMeaning
opportunityEntityIdrequiredThe numeric job id, the digits in linkedin.com/jobs/view/<id>. Get one from /discover/opportunities or /organization/opportunities.
Request
  curl 'https://api.rynora.dev/api/v2/opportunity/hiring-team?opportunityEntityId=value' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/opportunity/related-views1 credit

Get "people also viewed" opportunities

Parameters and response

Jobs that people who viewed this posting also viewed.

Reads the same rail as `similar`, because LinkedIn merged the two. The `signal` field says so rather than letting a caller believe they have a behavioural signal distinct from content similarity.

Query parameters
NameMeaning
opportunityEntityIdrequiredThe numeric job id, the digits in linkedin.com/jobs/view/<id>. Get one from /discover/opportunities or /organization/opportunities.
Request
  curl 'https://api.rynora.dev/api/v2/opportunity/related-views?opportunityEntityId=value' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/opportunity/similar1 credit

Get similar opportunities

Parameters and response

Postings LinkedIn lists alongside this one.

Query parameters
NameMeaning
opportunityEntityIdrequiredThe numeric job id, the digits in linkedin.com/jobs/view/<id>. Get one from /discover/opportunities or /organization/opportunities.
Request
  curl 'https://api.rynora.dev/api/v2/opportunity/similar?opportunityEntityId=value' \
    -H 'X-API-Key: $RYNORA_KEY'

Live API - Discover

Search LinkedIn for people, organizations and jobs you do not have yet. The slowest family and the only one that pages through results.

GET/api/v2/discover/opportunities2 credits

Discover opportunities (jobs)

Parameters and response

Search jobs by keyword and filters.

Everything else pages from one fetch.

Query parameters
NameMeaning
keywordoptionalFree-text search term.
sortByoptionalResult order. relevance is LinkedIn's own ranking; date_posted puts the newest first.
datePostedoptionalRestrict to postings from the last 24h, 1week or 1month.
experienceoptionalComma-separated seniority levels: internship, entry_level, associate, mid_senior, director, executive.
jobTypesoptionalComma-separated employment types: full_time, part_time, contract, temporary, internship, volunteer, other.
workplaceTypesoptionalComma-separated: onsite, remote, hybrid.
companiesoptionalComma-separated organization ids. Get one from /organization/resolve-slug.
industriesoptionalComma-separated industry names. Partial match against the industry line on the company page.
locationsoptionalComma-separated LinkedIn geo ids. Only the first is used: LinkedIn's job search takes a single geo, and sending several returns results for none of them.
functionsoptionalComma-separated LinkedIn job-function codes.
titlesoptionalComma-separated LinkedIn title ids.
easyApplyoptionaltrue returns only postings that can be applied to without leaving LinkedIn.
verifiedJoboptionaltrue returns only postings LinkedIn has verified.
countoptionalHow many results to return in this page.
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
refreshoptional
Request
  curl 'https://api.rynora.dev/api/v2/discover/opportunities' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/discover/organizations2 credits

Discover organizations

Parameters and response

Search companies by keyword, size, industry and headquarters.

Query parameters
NameMeaning
keywordrequiredFree-text search term.
headcountRangeoptionalComma-separated size buckets: 1-10, 11-50, 51-200, 201-500, 501-1000, 1001-5000, 5001-10000, 10001+.
industryoptionalComma-separated industries. Partial match.
geoEntityIdoptionalComma-separated LinkedIn geo ids for the headquarters.
countoptionalHow many results to return in this page.
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
refreshoptional
Request
  curl 'https://api.rynora.dev/api/v2/discover/organizations?keyword=software%20engineer' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/discover/people2 credits

Discover people

Parameters and response

Search people by keyword and structured filters.

Query parameters
NameMeaning
keywordoptionalFree-text search term.
firstNameoptionalComma-separated first names. Combine with lastName to identify one person.
lastNameoptionalComma-separated last names.
titleoptionalJob title across a person's positions. Partial and typo-tolerant. Combine with organization_slugs and current_only=true to find who holds a role at a company today.
currentCompanyoptionalOrganization id(s)
pastCompanyoptionalComma-separated organization ids for former employers: alumni and ex-employee sourcing.
schooloptionalComma-separated school ids.
industryoptionalComma-separated industries. Partial match.
geoEntityIdoptionalComma-separated LinkedIn geo ids for the headquarters.
profileLanguageoptionalComma-separated language codes, e.g. en. The language a profile is written in.
countoptionalHow many results to return in this page.
startoptionalZero-based offset into the result set. An alternative to cursor; both describe the same position.
Request
  curl 'https://api.rynora.dev/api/v2/discover/people' \
    -H 'X-API-Key: $RYNORA_KEY'

Live API - Contact

Work email and phone lookup, forwards and in reverse.

GET/api/v2/contact/email1 credit

Find a work email from a name and company domain

Parameters and response

Work out the most likely work email for someone, and say how sure it is.

Company mail follows a house pattern, so this builds the addresses that pattern could produce for the name given (`first.last@`, `flast@`, `first@`, and so on) and asks the domain's mail server which of them it will accept. The conversation stops one command short of sending: no message is delivered, and the person is not contacted.

**The employer comes from you, and `employment` says whether anything supports it.** This endpoint builds an address at the domain you name; it cannot know on its own that the person works there. So `confidence_score` answers "is this the right shape for an address at this domain", and `employment.status` answers "is this person even at this domain":

* `confirmed` - an address is on file for them there. * `contradicted` - they are on file at a different employer. The score is capped hard, because a well-shaped address for the wrong company looks exactly like a good answer otherwise. * `unknown` - nothing on file either way. The score is capped below the high band: the address is what somebody at that domain would have, and it assumes they are there.

**Read `status` before using the address.**

* `valid` - the mail server accepted this address and rejected one that cannot exist, so it is discriminating between real and fake recipients. This is the only status that means the mailbox was confirmed. * `catch_all` - the domain accepted an address made of random hex, so it accepts everything. The address returned is the most common pattern and is unconfirmed. * `unverifiable` - the question could not be asked, or the answer means nothing. Outbound port 25 blocked, greylisting, or a provider such as Microsoft 365 that answers 250 for every recipient. `reason` says which. * `invalid` - every candidate was rejected, or the domain has no MX record at all and cannot receive mail.

`confidence` compresses that to `high`, `low`, `guess` or `none` for callers who want one field rather than four.

Anything found there comes back under `stored`, and becomes the primary answer only when the probe could not produce a verified one.

Query parameters
NameMeaning
first_namerequiredThe person's given name, such as Jane. Accents are folded to ASCII and honorifics (Dr., Prof.) are stripped, so pass it as written on their profile.
last_namerequiredThe person's family name, such as Doe. Hyphenated names are kept whole (muller-schmidt), and post-nominals (PhD, Jr.) are stripped.
domainrequiredThe employer's mail domain, such as example.com. This is the domain their email is on, which is often not the domain their website is on - check the MX record if unsure. Do not pass a LinkedIn slug or a full URL.
verifyoptionaltrue asks the receiving mail server whether each candidate exists, which is what separates a verified address from a guess. false skips the network entirely and returns the most common pattern, which is fast and free but unconfirmed.
Request
  curl 'https://api.rynora.dev/api/v2/contact/email?first_name=Jane&last_name=Doe&domain=example.com' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "candidates": [
    {
      "accepted": "string|null",
      "code": "string|null",
      "email": "string",
      "pattern": "string"
    }
  ],
  "confidence": "string",
  "confidence_factors": [
    "string"
  ],
  "confidence_score": "integer",
  "domain": "string",
  "email": "string",
  "employment": {
    "employers_on_file": [
      "<empty>"
    ],
    "note": "string",
    "status": "string"
  },
  "first_name": "string",
  "full_name": "string",
  "house_pattern": {
    "agreement": "number",
    "confirmed": "boolean",
    "pattern": "string",
    "sample": "integer"
  },
  "last_name": "string",
  "mx": [
    "string"
  ],
  "pattern": "string",
  "probing_enabled": "boolean",
  "reason": "string",
  "source": "string",
  "status": "string",
  "stored": [
    "<empty>"
  ]
}
POST/api/v2/contact/known1 credit

Teach the finder an address you know is real

Parameters and response

Record an address you know to be correct, and teach the domain's pattern.

This is the only thing that fixes a domain nothing else can reach. Two cases defeat every other method:

* **Nothing is held at the domain.** The finder falls back to how the whole population writes addresses, and the population is not this company. A domain using `first.last` gets guessed as `flast`, because `flast` is what 46% of addresses look like overall. * **The domain is catch-all.** Its mail server accepts every recipient, including addresses made of random hex, so probing cannot tell a real mailbox from an invented one. Verification returns `catch_all` and nothing is learned.

One confirmed address settles both.

The effect is not limited to this person. Recording one address teaches the pattern for **everybody** at that domain, so the next lookup there is scored on company evidence rather than a population average.

JSON body
FieldTypeMeaning
first_namestring requiredThe person's given name, exactly as it corresponds to the address.
last_namestring requiredThe person's family name, exactly as it corresponds to the address.
emailstring requiredThe address you know is real, such as Jane.Doe@example.com. Case is not significant.
titlestring optionalTheir job title, if you have it. Stored but not used for matching.
organizationstring optionalThe employer's name, if you have it. Stored for display on lookups.
Request
  curl -X POST 'https://api.rynora.dev/api/v2/contact/known' \
    -H 'X-API-Key: $RYNORA_KEY' \
    -H 'Content-Type: application/json' \
    -d '{"first_name": "Jane", "last_name": "Doe", "email": "jane.doe@example.com"}'
GET/api/v2/contact/phone1 credit

Find a phone number from a name and company domain

Parameters and response

Phone numbers held for a person.

**Unlike the email endpoint, this cannot guess.** An email address is derivable - companies use a house pattern, so `first.last@domain` is a reasonable inference from a name. A phone number has no such structure: there is no function from a name to a number, so this returns what is on file or nothing at all. A `null` here means "not held", never "does not exist".

Numbers come back in E.164 (`+15555550100`) regardless of how they were written at the source, so they are directly dialable and comparable. A contact can hold more than one, in which case all are returned in the order they were recorded.

When several people share a name, pass `domain` to disambiguate. Without it every match is returned and `matches` tells you how many there were - treat a result with more than one match as ambiguous rather than picking the first.

Query parameters
NameMeaning
full_namerequiredThe person's full name, as it appears on their profile. Matched case-insensitively against stored contacts after whitespace is collapsed.
domainoptionalThe employer's domain, used to pick the right person when several share a name. Matched against both the contact's email domain and their employer's website host. Optional: omit it to search on name alone and read the organization on each match.
Request
  curl 'https://api.rynora.dev/api/v2/contact/phone?full_name=value' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/contact/reverse/email1 credit

Find the person behind an email address

Parameters and response

Who an email address belongs to.

A store lookup, not a live one: it answers from contacts already held and never reaches out to verify. An address we do not hold returns `found: false` - which means it is not in this store, and says nothing about whether the address is real or who owns it.

`is_free_provider` marks gmail, yahoo and the like. A personal address tells you far less about somebody's employer than a work address does, and a caller routing on the organization should check this before trusting it.

Query parameters
NameMeaning
emailrequiredThe address to look up. Case and surrounding whitespace are ignored; it is normalised before matching, so Jane.Doe@Example.com and jane.doe@example.com are the same query.
Request
  curl 'https://api.rynora.dev/api/v2/contact/reverse/email?email=jane.doe@example.com' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "domain": "string",
  "email": "string",
  "found": "boolean",
  "is_free_provider": "boolean",
  "person": "string|null"
}
GET/api/v2/contact/reverse/phone1 credit

Find the person behind a phone number

Parameters and response

Who a phone number belongs to.

A store lookup over the numbers held against contacts. Both sides are normalised to E.164 first, so the format the caller happens to use does not decide whether there is a match - which is the usual reason a reverse lookup wrongly reports nothing.

One number can return more than one person: shared switchboards and company main lines are recorded against everyone who listed them. All matches are returned rather than an arbitrary first, and `matches` says how many there were.

Query parameters
NameMeaning
phonerequiredThe number to look up, in any format. (555) 555-0100, 555-555-0100 and +15555550100 are the same query: it is converted to E.164 before matching. A ten-digit number with no country code is read as North American.
Request
  curl 'https://api.rynora.dev/api/v2/contact/reverse/phone?phone=(555)%20555-0100' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

{
  "found": "boolean",
  "matches": "integer",
  "people": [
    "<empty>"
  ],
  "phone": "string",
  "shared_line": "boolean"
}

Watchlist

Track people and companies over time and get the differences, by API or by email.

POST/api/v2/watchlist1 credit

Watch Add

Parameters and response

Start watching an entity.

Idempotent - re-posting the same URL reactivates it and updates the interval rather than creating a duplicate. The first poll only captures a baseline; changes are reported from the second onwards.

JSON body
FieldTypeMeaning
urlstring requiredProfile URL, company URL, or company slug
kindstring optionalperson or company; inferred if omitted
interval_daysnumber optional Default 1.0.
notestring optional
track_activityboolean optionalAlso record new posts. Off by default: posts appear weekly where job moves appear every few years, so tracking everyone would bury the signal. Default false.
Request
  curl -X POST 'https://api.rynora.dev/api/v2/watchlist' \
    -H 'X-API-Key: $RYNORA_KEY' \
    -H 'Content-Type: application/json' \
    -d '{"url": "https://www.linkedin.com/in/jane-doe-example/"}'
GET/api/v2/watchlist1 credit

Watch List

Parameters and response

Everything you are watching, oldest first.

Scoped to the key that calls it. An operator key, which has no owner, sees every entry regardless of who added it.

Query parameters
NameMeaning
include_inactiveoptionalInclude entities removed from the watchlist. Their change history is kept, so this shows what was once watched.
Request
  curl 'https://api.rynora.dev/api/v2/watchlist' \
    -H 'X-API-Key: $RYNORA_KEY'
DELETE/api/v2/watchlist0 credits

Watch Remove

Parameters and response

Stop watching one entity, or empty the watchlist.

Deactivates rather than deletes: the recorded changes are the valuable part and dropping the entries would orphan them. Re-adding any URL later reactivates it with its history intact, so this is reversible.

Pass `url` to stop one, or `clear=true` to stop all. Passing neither is an error rather than a no-op, and passing both is an error rather than a guess at which was meant.

Scoped to your key: `clear=true` empties your own watchlist, not anybody else's. An operator key, which has no owner, is the exception and clears everything.

Query parameters
NameMeaning
urlone of theseThe LinkedIn profile or company URL of the entity to stop watching. Omit it and pass clear=true to empty the whole watchlist instead. Provide exactly one of the identifiers marked “one of these”.
clearoptionaltrue stops watching everything you are watching. Required explicitly: a DELETE that emptied the watchlist merely because a url was left off would turn a mistyped request into a wiped list. Recorded changes are kept either way.
Request
  curl -X DELETE 'https://api.rynora.dev/api/v2/watchlist?url=https://www.linkedin.com/in/jane-doe-example/' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/watchlist/changes1 credit

Watch Changes

Parameters and response

Recorded changes, newest first.

Query parameters
NameMeaning
urlone of theseHistory for one entity Provide exactly one of the identifiers marked “one of these”.
jobs_onlyoptionalOnly moves between employers
posts_onlyoptionalOnly newly published posts
contacts_onlyoptionalOnly where an email or phone became visible
sourceoptionalprofile or activity
limitoptionalHow many results to return in this page.
Request
  curl 'https://api.rynora.dev/api/v2/watchlist/changes?url=https://www.linkedin.com/in/jane-doe-example/' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

[
  {
    "changes": [
      {
        "after": {
          "linkedin_url": "string",
          "posted_date": "string",
          "text": "string",
          "urn": "string"
        },
        "before": "string|null",
        "field": "string",
        "kind": "string",
        "summary": "string"
      }
    ],
    "count": "integer",
    "detected_at": "string",
    "entity_id": "string",
    "has_job_change": "boolean",
    "has_new_contact": "boolean",
    "has_new_posts": "boolean",
    "kind": "string",
    "linkedin_url": "string",
    "source": "string"
  }
]
GET/api/v2/watchlist/confirm1 credit · no key needed

Confirm a subscription

Parameters and response

Start receiving watchlist changes at this address.

Public, for the same reason unsubscribing is: the person confirming owns the inbox, not necessarily an API key.

Query parameters
NameMeaning
tokenrequiredThe token from a confirmation link.
Request
  curl 'https://api.rynora.dev/api/v2/watchlist/confirm?token=the-token-from-your-email'
POST/api/v2/watchlist/subscribers2 credits

Email watchlist changes to an address

Parameters and response

Send an address the changes detected on your watchlist.

Every poll that finds something emails the people listening to that entity. The mail names what moved - a headline edit, a new position, a published post - and links back to the full change log.

**An address receives nothing until it is confirmed.** Anyone can be typed into this endpoint, so a mistyped address would otherwise be mailed on the next poll - somebody who never asked, about people they have never heard of. Your own account address is confirmed on sight. Any other address starts pending and is sent nothing at all, not even a confirmation, unless you pass `send_confirmation=true`; otherwise the link comes back to you to pass on however you already talk to that person.

Idempotent on the address: subscribing twice updates the subscription rather than sending everything twice.

Each email carries an unsubscribe link that works without signing in. A notification nobody can stop is a complaint waiting to happen, and in most jurisdictions a legal problem.

Costs 2 credits. The polling it causes is billed separately, per entity, when a poll actually runs.

JSON body
FieldTypeMeaning
emailstring requiredWhere to send change notifications. Does not need an account on this service: the person who wants to hear that somebody changed jobs is often not the person holding the API key.
urlsarray optionalLimit notifications to these entities. Omit for everything on your watchlist. Narrowing matters more than it looks - somebody tracking twelve people does not want the whole team's watchlist in their inbox.
send_confirmationboolean optionaltrue emails the address a confirmation link. false, the default, sends them nothing and returns the link to you instead, so a mistyped address never receives mail from this service at all. Your own account address needs neither: it is confirmed on sight. Default false.
Request
  curl -X POST 'https://api.rynora.dev/api/v2/watchlist/subscribers' \
    -H 'X-API-Key: $RYNORA_KEY' \
    -H 'Content-Type: application/json' \
    -d '{"email": "jane.doe@example.com"}'
GET/api/v2/watchlist/subscribers2 credits

Who is being emailed

Parameters and response

Addresses receiving your watchlist's changes.

Request
  curl 'https://api.rynora.dev/api/v2/watchlist/subscribers' \
    -H 'X-API-Key: $RYNORA_KEY'
Response

The fields below appear inside data. Types are shown in place of values; string|null means the field is always present and may be null.

[
  "<empty>"
]
DELETE/api/v2/watchlist/subscribers2 credits

Stop emailing an address

Parameters and response

Stop notifications to one address. Costs nothing, like any other stop.

Query parameters
NameMeaning
emailrequiredThe address to stop emailing.
Request
  curl -X DELETE 'https://api.rynora.dev/api/v2/watchlist/subscribers?email=jane.doe@example.com' \
    -H 'X-API-Key: $RYNORA_KEY'
GET/api/v2/watchlist/unsubscribe1 credit · no key needed

Unsubscribe from an emailed link

Parameters and response

Stop notifications using the link in the email.

On a router without the API key requirement, because the person who wants the mail to stop is often not the person who holds a key.

Query parameters
NameMeaning
tokenrequiredThe token from the unsubscribe link in a notification email.
Request
  curl 'https://api.rynora.dev/api/v2/watchlist/unsubscribe?token=the-token-from-your-email'