# pineapi: full documentation --- > pineapi is a LinkedIn scraping API that searches profiles by filter and returns them as structured data, billed per profile delivered. --- # Use pineapi from your assistant Paste the block into an assistant with HTTP execution tools. A chat without tools cannot make API calls. This is an integration brief, not an installed connector or MCP server. ```text pineapi — LinkedIn profile extraction over HTTP. You are driving it for me. Base URL: ask me for PINEAPI_BASE_URL; the production API host is not yet confirmed. Do not send credentials to the website or guess an API hostname. Auth: Authorization: Bearer . Keep the key private. Cost: $0.012 per profile delivered, prepaid. Search minimum $0.12; lookup no floor. Never request more than I asked for. Explain the maximum cost and ask before a run over $5. No sandbox, test credit, free trial, email enrichment or MCP server. Both creation POSTs require an Idempotency-Key and return 202 with run_id. Generate a unique key for each new operation; persist it with the exact body. For a retry of the same operation, reuse that key and unchanged body. 1. Search: POST /v1/profile-searches {"locations":["Spain"],"current_job_titles":["Data Engineer"],"max_items":100} max_items: multiple of 25, from 25 to 2000, default 100. Read closed vocabularies, do not guess values: GET /v1/catalogs/industries, /locations, /schools, /experience-bands, /company-headcounts. Experience bands apply to years_of_experience and years_at_current_company; headcounts apply to company_headcount. 2. Lookup: POST /v1/profile-lookups {"linkedin_urls":["https://www.linkedin.com/in/"]} 1–2000 URLs, normalized and deduplicated; no blocks of 25 or search floor. Poll GET /v1/runs/{run_id} until succeeded, partial, failed or cancelled. Poll every 5 seconds for the first minute, then every 20 seconds. Lookup usually takes seconds, search minutes. These are not deadlines or an SLA. Queued, starting, running and processing are not final. Preserve run_id on timeout. Read /v1/runs/{run_id}/profiles?limit=50; pass next_cursor as cursor unchanged until has_more is false. No offset. CSV: /v1/runs/{run_id}/profiles.csv. Report profiles_delivered, charged, status and unresolved[] from the run. Match lookup rows to inputs by requested_url, never the current linkedin_url slug. Unresolved not_found: dead/renamed URL; unavailable: worth retrying later. Unresolved URLs cost nothing. Separate runs may bill the same profile again. Within one run a duplicate row is not delivered or charged twice. Partial is a normal terminal outcome: fewer profiles arrived than requested. experience_truncated / skills_truncated flag incomplete source sections; total_months_experience can therefore be an undercount. No email addresses. start_page is unstable search pagination; prefer one appropriately sized run. Results expire after 30 days. Save lawful copies before expiry. Errors: {"error":{"type":"…","code":"…","message":"…","param":null}}. 401 missing_api_key / invalid_api_key: stop and ask for a valid credential. 403 insufficient_scope: ask for a key with the required scope; do not retry blindly. 422 invalid_parameter / start_page_out_of_range: correct the named input. 402 insufficient_balance: stop and ask me to top up; minimum top-up $5. 429: wait Retry-After seconds before retrying; retain the idempotency key/body. 409 idempotency_key_reused: do not change the body of an existing operation. Missing Idempotency-Key is rejected: add it before starting a new operation. For transient HTTP failures, retry cautiously with the same key/body. Poll authenticated run status; webhook signing-secret access is not yet available. ``` # Quickstart Run these commands in one Bash session. There is no sandbox: creation requests are paid. ## 1. Configure the confirmed API host and key ```bash # Sign up in the API dashboard, top up by card (minimum $5), then mint an API key. # The dashboard/production API URL will be supplied at onboarding. # API keys cannot create keys or read billing; those require a signed-in session. export PINEAPI_BASE_URL='' read -r -s PINEAPI_KEY export PINEAPI_KEY # Requires curl, jq and uuidgen. Paid requests start in step 3. ``` ## 2. Read the accepted filters ```bash curl --fail-with-body "$PINEAPI_BASE_URL/v1/catalogs/locations" \ -H "Authorization: Bearer $PINEAPI_KEY" # Also read industries, schools, experience-bands and company-headcounts as needed. ``` ## 3. Create one paid search ```bash # Up to 100 delivered profiles: $1.20. A completed empty search costs $0.12. # Keep the key and body unchanged if retrying this request. IDEMPOTENCY_KEY=$(uuidgen) RUN_ID=$(curl --fail-with-body "$PINEAPI_BASE_URL/v1/profile-searches" \ -H "Authorization: Bearer $PINEAPI_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H 'Content-Type: application/json' \ -d '{"locations":["Spain"],"current_job_titles":["Data Engineer"],"max_items":100}' | jq -er '.run_id') || exit 1 printf '%s\n' "$RUN_ID" # Save this ID to resume polling later. ``` ## 4. Poll until the run ends ```bash STARTED=$SECONDS while true; do RUN=$(curl --fail-with-body "$PINEAPI_BASE_URL/v1/runs/$RUN_ID" \ -H "Authorization: Bearer $PINEAPI_KEY") || exit 1 STATUS=$(printf '%s' "$RUN" | jq -r '.status') case "$STATUS" in succeeded|partial) printf '%s\n' "$RUN"; break ;; failed|cancelled) printf '%s\n' "$RUN"; exit 1 ;; queued|starting|running|processing) ;; *) printf 'Unexpected status: %s\n' "$STATUS"; exit 1 ;; esac if (( SECONDS - STARTED >= 1800 )); then printf 'Polling paused; resume with run_id %s\n' "$RUN_ID"; exit 1 fi if (( SECONDS - STARTED < 60 )); then sleep 5; else sleep 20; fi done # HTTP errors stop this example. On 429 wait Retry-After before resuming. ``` ## 5. Read every page ```bash CURSOR='' while true; do PAGE=$(curl --fail-with-body --get "$PINEAPI_BASE_URL/v1/runs/$RUN_ID/profiles" \ -H "Authorization: Bearer $PINEAPI_KEY" \ --data-urlencode 'limit=50' --data-urlencode "cursor=$CURSOR") || exit 1 printf '%s' "$PAGE" | jq '.data[]' [ "$(printf '%s' "$PAGE" | jq -r '.has_more')" = true ] || break CURSOR=$(printf '%s' "$PAGE" | jq -er '.next_cursor') || exit 1 done ``` # Integration guides ## Search or lookup? Use filters to discover profiles; max_items is a multiple of 25 between 25 and 2,000. Use lookup when you already have 1–2,000 profile URLs. Lookup normalizes and deduplicates that list, has no search floor and does not use start_page. ## Polling and webhook notifications Polling is the integration path you can implement now: every 5 seconds for the first minute, then every 20 seconds. A webhook_url can receive run.succeeded, run.partial, run.failed or run.cancelled with data.run matching the status response. Profiles are fetched separately. Customer access to the signing secret is not implemented yet, so authenticated polling remains the recommended source of truth; do not trust an unverified incoming notification as proof of completion. ## Partial is a result, not an HTTP error The terminal states are succeeded, partial, failed and cancelled. Partial means the run delivered less than requested; inspect profiles_delivered, failure, pagination and charged. Delivered rows are billed, with the search minimum applying. HTTP 200 when reading a run only means that the status request succeeded. ## Reconcile a batch of URLs Join returned profiles to inputs using requested_url. linkedin_url may contain a newer vanity slug. The run’s unresolved array reports requested_url, reason and detail for URLs without a usable profile: not_found means absent or renamed; unavailable may be worth retrying. Neither is billed. Deduplicate your original list before comparing counts. ## Know when data is incomplete The source measurements found roughly 40% of profiles with truncated experience and 68% with truncated skills. The per-row experience_truncated and skills_truncated flags let you identify them. total_months_experience is an undercount when work history is incomplete. Current role is derived from work history because the source current-position section was empty on 13 of 25 profiles in a verification sample; these are observations, not coverage guarantees. ## Search pages are not result cursors The predecessor’s repeated searches showed 30% overlap between consecutive pages and a drifting reported total in 28% of repeated searches. Prefer one run with an appropriate max_items over several start_page requests. In contrast, downloaded run results use next_cursor: pass it back as cursor until has_more is false. ## Understand the invoice The bill counts inserted profile rows, not requested profiles or raw source items. Each delivered profile costs $0.012. Search has a $0.12 minimum; lookup has no floor. A duplicate inside one run is discarded; the same profile in a later run is billed again. Card top-ups start at $5 and credit does not expire. GET /v1/usage and /v1/me require a signed-in session, not an API key. No subscription, seat fee or monthly minimum. Unused paid balance is not refundable on closure except where legally required or charges were incorrect. # Generated profile schema **Profile fields**: Generated from the API OpenAPI schema. Nullable values can be absent from the source profile. - `id` (string (uuid)): Id - `ordinal` (integer): Ordinal - `public_identifier` (string | null): Public Identifier - `linkedin_url` (string): Linkedin Url - `requested_url` (string | null): Requested Url - `first_name` (string | null): First Name - `last_name` (string | null): Last Name - `full_name` (string | null): Full Name - `headline` (string | null): Headline - `about` (string | null): About - `location_text` (string | null): Location Text - `location_city` (string | null): Location City - `location_state` (string | null): Location State - `location_region_code` (string | null): Location Region Code - `location_country` (string | null): Location Country - `location_country_full` (string | null): Location Country Full - `location_country_code` (string | null): Location Country Code - `open_to_work` (boolean | null): Open To Work - `hiring` (boolean | null): Hiring - `premium` (boolean | null): Premium - `influencer` (boolean | null): Influencer - `creator` (boolean | null): Creator - `verified` (boolean | null): Verified - `open_profile` (boolean | null): Open Profile - `memorialized` (boolean | null): Memorialized - `connections_count` (integer | null): Connections Count - `follower_count` (integer | null): Follower Count - `registered_at` (string (date-time) | null): Registered At - `profile_language` (string | null): Profile Language - `profile_country` (string | null): Profile Country - `profile_picture_url` (string | null): Profile Picture Url - `photo_url` (string | null): Photo Url - `cover_picture_url` (string | null): Cover Picture Url - `current_title` (string | null): Current Title - `current_company_name` (string | null): Current Company Name - `current_company_linkedin_url` (string | null): Current Company Linkedin Url - `current_position_started_on` (string (date) | null): Current Position Started On - `total_months_experience` (integer | null): Total Months Experience - `months_at_current_company` (integer | null): Months At Current Company - `months_in_current_role` (integer | null): Months In Current Role - `promoted_at_current_company` (boolean): Promoted At Current Company - `promotions_count` (integer): Promotions Count - `companies_count` (integer): Companies Count - `avg_months_per_company` (integer | null): Avg Months Per Company - `linkedin_member_since` (string (date) | null): Linkedin Member Since - `skills_flat` (string[]): Skills Flat - `languages_flat` (string[]): Languages Flat - `school_names` (string[]): School Names - `company_names` (string[]): Company Names - `section_totals` (object | null): Section Totals - `experience_truncated` (boolean): Experience Truncated - `skills_truncated` (boolean): Skills Truncated - `scraped_at` (string (date-time)): Scraped At - `education[]`: Education Sub-fields: . - `certifications[]`: Certifications Sub-fields: . - `languages[]`: Languages Sub-fields: . - `skills[]`: Skills Sub-fields: . - `projects[]`: Projects Sub-fields: . - `publications[]`: Publications Sub-fields: . - `courses[]`: Courses Sub-fields: . - `honors[]`: Honors Sub-fields: . - `volunteering[]`: Volunteering Sub-fields: . - `recommendations[]`: Recommendations Sub-fields: . - `interests[]`: Interests Sub-fields: . - `patents[]`: Patents Sub-fields: . - `organizations[]`: Organizations Sub-fields: . - `websites[]`: Websites Sub-fields: . - `top_skills[]`: Top Skills Sub-fields: . - `experiences[]`: Experiences Sub-fields: ordinal, position, company_name, company_linkedin_url, employment_type, workplace_type, location, description, started_on, ended_on, is_current, duration_months, duration_text, skills. # Anonymised demo profile ```json { "id": "00000000-0000-0000-0000-0000000000de", "ordinal": 0, "public_identifier": "morgan-duarte-006", "linkedin_url": "https://www.linkedin.com/in/redacted-1383", "requested_url": null, "first_name": "Morgan", "last_name": "Duarte", "full_name": "Morgan Duarte", "headline": "Working across product and engineering teams to ship things people use. Interested in systems ", "about": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they ", "location_text": "Spain", "location_city": null, "location_state": null, "location_region_code": null, "location_country": "Spain", "location_country_full": "Spain", "location_country_code": "ES", "open_to_work": false, "hiring": false, "premium": true, "influencer": false, "creator": false, "verified": true, "open_profile": false, "memorialized": false, "connections_count": 1416, "follower_count": 1471, "registered_at": "2014-06-14T16:55:49.394000Z", "profile_language": "en", "profile_country": "US", "profile_picture_url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "photo_url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "cover_picture_url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "current_title": "Senior Software Engineer", "current_company_name": "Covalent", "current_company_linkedin_url": "https://www.linkedin.com/company/covalent-material-insights/", "current_position_started_on": "2026-01-01", "total_months_experience": 284, "months_at_current_company": 8, "months_in_current_role": 8, "promoted_at_current_company": false, "promotions_count": 4, "companies_count": 20, "avg_months_per_company": 14, "linkedin_member_since": "2014-06-14", "skills_flat": [ "Online Privacy", "ePrivacy" ], "languages_flat": [ "Catalán", "English" ], "school_names": [ "Self-training in Front-end & Back-end development: PhP, Ruby on Rails", "Self-training in Front-end development: HTML, CSS, JavaScript, jQuery, PhP", "Izhevsk State Technical University (ISTU)" ], "company_names": [ "Alex Zappa", "Cirkus: Scheduling & Project Management", "Compettia - Atrivity", "ConsentTheater", "Costa Blanca, Ltd.", "Covalent", "DataFox", "FreshJuice", "Harness", "HeadsUP! , Inc", "Highway 92 Succulents", "Iru", "Kandji", "LoanMart", "Mammoth Hunters", "Marna Digital", "Mitor Digital", "Orange Marketing", "The Navio Group", "Trovata.io" ], "section_totals": { "experience": 25, "education": 3, "certifications": 19, "receivedRecommendations": 13, "skills": 64, "courses": 0, "languages": 4, "projects": 11, "volunteering": 0, "publications": 0, "patents": 0, "organizations": 0, "honorsAndAwards": 0, "causes": 4 }, "experience_truncated": true, "skills_truncated": true, "education": [ { "schoolName": "Self-training in Front-end & Back-end development: PhP, Ruby on Rails", "description": null, "insights": null, "skills": null, "schoolLinkedinUrl": "https://www.linkedin.com/search/results/all/?keywords=Self-training+in+Front-end+%26+Back-end+development%3A+PhP%2C+Ruby+on+Rails", "schoolId": null, "degree": null, "fieldOfStudy": null, "period": "2016 - 2017", "startDate": { "year": 2016, "text": "2016" }, "endDate": { "year": 2017, "text": "2017" }, "schoolLogo": null }, { "schoolName": "Self-training in Front-end development: HTML, CSS, JavaScript, jQuery, PhP", "description": null, "insights": null, "skills": null, "schoolLinkedinUrl": "https://www.linkedin.com/search/results/all/?keywords=Self-training+in+Front-end+development%3A+HTML%2C+CSS%2C+JavaScript%2C+jQuery%2C+PhP", "schoolId": null, "degree": null, "fieldOfStudy": null, "period": "2006 - 2013", "startDate": { "year": 2006, "text": "2006" }, "endDate": { "year": 2013, "text": "2013" }, "schoolLogo": null }, { "schoolName": "Izhevsk State Technical University (ISTU)", "description": null, "insights": null, "skills": null, "schoolLinkedinUrl": "https://www.linkedin.com/company/15099078/", "schoolId": "15099078", "degree": "Bachelor of Science - BS", "fieldOfStudy": "Computer Science (Incomplete)", "period": "2003 - 2006", "startDate": { "year": 2003, "text": "2003" }, "endDate": { "year": 2006, "text": "2006" }, "schoolLogo": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "sizes": [ { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 400, "height": 400 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 200, "height": 200 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 100, "height": 100 } ] } } ], "certifications": [ { "title": "HubSpot Architecture I: Data Models and APIs", "issuedBy": "HubSpot Academy", "issuedAt": "Issued May 2023 · Expired May 2025", "link": "https://app.hubspot.com/academy/achievements/g3gfph5y/en/1/alex-zappa/hubspot-architecture-i-data-models-and-apis", "issuedByLink": "https://www.linkedin.com/company/16197478/", "issuedByLogo": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "sizes": [ { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 400, "height": 400 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 200, "height": 200 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 100, "height": 100 } ] } }, { "title": "Digital Marketing ", "issuedBy": "HubSpot Academy", "issuedAt": "Issued Apr 2023 · Expired May 2025", "link": "https://app.hubspot.com/academy/achievements/mcs5ybn8/en/1/alex-zappa/digital-marketing", "issuedByLink": "https://www.linkedin.com/company/16197478/", "issuedByLogo": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "sizes": [ { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 400, "height": 400 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 200, "height": 200 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 100, "height": 100 } ] } } ], "languages": [ { "name": "Catalán", "proficiency": "Limited working proficiency" }, { "name": "English", "proficiency": "Native or bilingual proficiency" } ], "skills": [ { "name": "Online Privacy", "positions": [ "Creator & Maintainer at ConsentTheater" ] }, { "name": "ePrivacy", "positions": [ "Creator & Maintainer at ConsentTheater" ] } ], "projects": [ { "title": "FreshJuice", "duration": "Jan 2024 - Present", "startDate": { "month": "Jan", "year": 2024, "text": "Jan 2024" }, "endDate": { "text": "Present" }, "description": null }, { "title": "Kandji.io — Marketing Website", "duration": "Sep 2021 - Sep 2025", "startDate": { "month": "Sep", "year": 2021, "text": "Sep 2021" }, "endDate": { "month": "Sep", "year": 2025, "text": "Sep 2025" }, "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engi" } ], "publications": [], "courses": [], "honors": [], "volunteering": [], "recommendations": [ { "givenBy": "Avery Duarte", "givenAt": "March 9, 2026", "givenByLink": "https://www.linkedin.com/in/redacted-6180", "givenByHeadline": "Sam Bianchi", "givenByPicture": null, "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use." }, { "givenBy": "Morgan Rivera", "givenAt": "December 13, 2025", "givenByLink": "https://www.linkedin.com/in/redacted-6344", "givenByHeadline": "Sam Rivera", "givenByPicture": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "sizes": [ { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 800, "height": 800 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 400, "height": 400 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 200, "height": 200 }, { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg", "width": 100, "height": 100 } ] }, "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineer" } ], "interests": [ { "interestName": "Top Voices", "elements": [ { "title": "Megan Lieu", "subtitle": "Developer Advocate & Founder @ ML Data | Data Science & AI Content Creator", "link": "https://www.linkedin.com/in/redacted-1812", "caption": "230,965 followers", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } }, { "title": "Yamini Rangan", "subtitle": "CEO at HubSpot | Helping millions grow better", "link": "https://www.linkedin.com/in/redacted-8822", "caption": "181,922 followers", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } } ] }, { "interestName": "Companies", "elements": [ { "title": "PlayStation", "subtitle": null, "link": "https://www.linkedin.com/company/1254/", "caption": "1,052,801 followers", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } }, { "title": "Google", "subtitle": null, "link": "https://www.linkedin.com/company/1441/", "caption": "42,357,999 followers", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } } ] }, { "interestName": "Groups", "elements": [ { "title": "GDPR General Data Protection Regulation Technology", "subtitle": null, "link": "https://www.linkedin.com/groups/12017677", "caption": "34,569 members", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } }, { "title": "Information Security Network", "subtitle": null, "link": "https://www.linkedin.com/groups/80784", "caption": "734,208 members", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } } ] }, { "interestName": "Newsletters", "elements": [ { "title": "Going CLEAR: Warmly GTM", "subtitle": "GTM, RevOps & Founder Advice from Maximus Greenwald who posts live actual monthly revenue numbers", "link": "https://www.linkedin.com/newsletters/7174798574000439296", "caption": "Published weekly", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } }, { "title": "GDPR StepWise™ ", "subtitle": "Building StepWise™... a fixed-price, 10-step roadmap turning compliance into the structure businesses need to grow.", "link": "https://www.linkedin.com/newsletters/7417448553255694336", "caption": "Published weekly", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } } ] }, { "interestName": "Schools", "elements": [ { "title": "Y Combinator", "subtitle": null, "link": "https://www.linkedin.com/school/167872/", "caption": "1,811,337 followers", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } }, { "title": "Regenera", "subtitle": null, "link": "https://www.linkedin.com/school/70890416/", "caption": "10,157 followers", "image": { "url": "https://media.licdn.com/dms/image/fixture/morgan-duarte-006.jpg" } } ] } ], "patents": [], "organizations": [], "websites": [], "top_skills": [ "HubSpot CMS Developer", "JAMstack", "High Performance Web Sites", "JavaScript", "Tailwind CSS" ], "experiences": [ { "ordinal": 0, "position": "Senior Software Engineer", "company_name": "Covalent", "company_linkedin_url": "https://www.linkedin.com/company/covalent-material-insights/", "employment_type": "Contract", "workplace_type": "Remote", "location": "Sunnyvale, California, United States", "description": null, "started_on": "2026-01-01", "ended_on": null, "is_current": true, "duration_months": 8, "duration_text": "9 mos", "skills": [ "React.js", "Next.js", "Astro", "WordPress", "C#", "Software Development", "SaaS Development", "AngularJS" ] }, { "ordinal": 1, "position": "Creator & Maintainer", "company_name": "ConsentTheater", "company_linkedin_url": "https://www.linkedin.com/company/consenttheater/", "employment_type": "Self-employed", "workplace_type": "Remote", "location": "Worldwide", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering ", "started_on": "2025-08-01", "ended_on": null, "is_current": true, "duration_months": 13, "duration_text": "1 yr 2 mos", "skills": [ "Online Privacy", "General Data Protection Regulation (GDPR)", "Open Source", "ePrivacy" ] }, { "ordinal": 2, "position": "Founder and Software Engineer", "company_name": "FreshJuice", "company_linkedin_url": "https://www.linkedin.com/company/freshjuice/", "employment_type": null, "workplace_type": "Remote", "location": "☀️ Sunny Spain", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things", "started_on": "2024-01-01", "ended_on": null, "is_current": true, "duration_months": 32, "duration_text": "2 yrs 9 mos", "skills": [ "Eleventy", "High Performance Web Sites", "Web Content Accessibility Guidelines (WCAG)", "JAMstack", "Front-End Development", "Search Engine Optimization (SEO)", "HubSpot", "Software Development", "Alpine.js", "SEO Audits", "Tailwind CSS", "Node.js", "React.js", "Rust (Programming Language)" ] }, { "ordinal": 3, "position": "Technical Fellow", "company_name": "Orange Marketing", "company_linkedin_url": "https://www.linkedin.com/company/orangemarketing1/", "employment_type": "Contract", "workplace_type": "Remote", "location": "Newport Beach, California, United States", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people us", "started_on": "2025-06-01", "ended_on": null, "is_current": true, "duration_months": 15, "duration_text": "1 yr 4 mos", "skills": [] }, { "ordinal": 4, "position": "Senior Software Engineer", "company_name": "Orange Marketing", "company_linkedin_url": "https://www.linkedin.com/company/orangemarketing1/", "employment_type": "Contract", "workplace_type": "Remote", "location": "Newport Beach, California, United States", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that st", "started_on": "2019-06-01", "ended_on": "2025-06-01", "is_current": false, "duration_months": 72, "duration_text": "6 yrs 1 mo", "skills": [ "Visual Web Developer", "High Performance Web Sites", "Web Content Accessibility Guidelines (WCAG)", "JAMstack", "Front-End Development", "React.js", "HubSpot", "Software Development", "SEO Audits", "Tailwind CSS", "Node.js" ] }, { "ordinal": 5, "position": "Full Stack Developer", "company_name": "LoanMart", "company_linkedin_url": "https://www.linkedin.com/company/wheels-financial-group-dba-800loanmart/", "employment_type": "Contract", "workplace_type": "Remote", "location": "Los Angeles, California, United States", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as t", "started_on": "2019-06-01", "ended_on": null, "is_current": true, "duration_months": 87, "duration_text": "7 yrs 4 mos", "skills": [] }, { "ordinal": 6, "position": "Independent Software Engineer", "company_name": "Alex Zappa", "company_linkedin_url": "https://www.linkedin.com/company/alex-zappa/", "employment_type": "Freelance", "workplace_type": "Remote", "location": "planet Earth, Solar system, Milky Way galaxy", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay s", "started_on": "2014-08-01", "ended_on": null, "is_current": true, "duration_months": 145, "duration_text": "12 yrs 2 mos", "skills": [ "Visual Web Developer", "Vue.js", "Eleventy", "High Performance Web Sites", "Full-Stack Development", "Web Content Accessibility Guidelines (WCAG)", "JAMstack", "Front-End Development", "PHP", "React.js", "Astro", "Search Engine Optimization (SEO)", "HubSpot", "Python (Programming Language)", "Software Development", "Alpine.js", "SEO Audits", "Tailwind CSS", "Web Applications", "User Experience (UX)", "WordPress", "Node.js" ] }, { "ordinal": 7, "position": "Senior Web Developer", "company_name": "Iru", "company_linkedin_url": "https://www.linkedin.com/company/officiallyiru/", "employment_type": "Contract", "workplace_type": "Remote", "location": "Miami, Florida, United States", "description": null, "started_on": "2025-09-01", "ended_on": "2026-02-01", "is_current": false, "duration_months": 5, "duration_text": "6 mos", "skills": [ "FreshJuice.dev", "HubSpot CMS Developer", "Alpine.js", "JavaScript", "Front-End Development", "Tailwind CSS", "HubDB", "API Development" ] }, { "ordinal": 8, "position": "Senior Web Developer", "company_name": "Kandji", "company_linkedin_url": "https://www.linkedin.com/company/kandji/", "employment_type": "Full-time", "workplace_type": "On-site", "location": "London, England, United Kingdom", "description": null, "started_on": "2025-03-01", "ended_on": "2025-10-01", "is_current": false, "duration_months": 7, "duration_text": "8 mos", "skills": [ "Eleventy", "High Performance Web Sites", "Web Content Accessibility Guidelines (WCAG)", "JAMstack", "Front-End Development", "React.js", "HubSpot", "SEO Audits", "Tailwind CSS", "Node.js", "FreshJuice.dev" ] }, { "ordinal": 9, "position": "Sr. Web Developer", "company_name": "Kandji", "company_linkedin_url": "https://www.linkedin.com/company/kandji/", "employment_type": "Contract", "workplace_type": "Remote", "location": "Miami, Florida, United States", "description": null, "started_on": "2022-11-01", "ended_on": "2025-03-01", "is_current": false, "duration_months": 28, "duration_text": "2 yrs 5 mos", "skills": [ "Eleventy", "High Performance Web Sites", "Web Content Accessibility Guidelines (WCAG)", "JAMstack", "Front-End Development", "React.js", "HubSpot", "SEO Audits", "Tailwind CSS", "Node.js" ] }, { "ordinal": 10, "position": "Web Developer", "company_name": "Kandji", "company_linkedin_url": "https://www.linkedin.com/company/kandji/", "employment_type": "Freelance", "workplace_type": "Remote", "location": "San Francisco, California, United States", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and e", "started_on": "2019-07-01", "ended_on": "2022-11-01", "is_current": false, "duration_months": 40, "duration_text": "3 yrs 5 mos", "skills": [ "Front-End Development", "Node.js" ] }, { "ordinal": 11, "position": "Web Developer", "company_name": "Trovata.io", "company_linkedin_url": "https://www.linkedin.com/company/trovata-io/", "employment_type": "Freelance", "workplace_type": "Remote", "location": "San Diego, California, United States", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. W", "started_on": "2019-02-01", "ended_on": "2023-11-01", "is_current": false, "duration_months": 57, "duration_text": "4 yrs 10 mos", "skills": [ "Visual Web Developer", "Front-End Development", "PHP", "React.js", "HubSpot", "WordPress", "Node.js" ] }, { "ordinal": 12, "position": "Chief Technology Officer", "company_name": "Marna Digital", "company_linkedin_url": "https://www.linkedin.com/company/marna-digital/", "employment_type": "Freelance", "workplace_type": "Hybrid", "location": "Alicante, Valencian Community, Spain", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship t", "started_on": "2018-02-01", "ended_on": "2022-12-01", "is_current": false, "duration_months": 58, "duration_text": "4 yrs 11 mos", "skills": [] }, { "ordinal": 13, "position": "Full Stack Engineer", "company_name": "Mitor Digital", "company_linkedin_url": "https://www.linkedin.com/search/results/all/?keywords=Mitor+Digital", "employment_type": "Freelance", "workplace_type": null, "location": "Alicante, Valencian Community, Spain", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Intere", "started_on": "2018-11-01", "ended_on": "2021-04-01", "is_current": false, "duration_months": 29, "duration_text": "2 yrs 6 mos", "skills": [ "Front-End Development" ] }, { "ordinal": 14, "position": "Full Stack Developer", "company_name": "Highway 92 Succulents", "company_linkedin_url": "https://www.linkedin.com/company/highway-92-succulents/", "employment_type": "Freelance", "workplace_type": null, "location": "San Francisco Bay Area", "description": "Working across product and engi", "started_on": "2016-12-01", "ended_on": "2020-03-01", "is_current": false, "duration_months": 39, "duration_text": "3 yrs 4 mos", "skills": [ "Visual Web Developer" ] }, { "ordinal": 15, "position": "Wordpress Developer", "company_name": "The Navio Group", "company_linkedin_url": "https://www.linkedin.com/company/the-navio-group/", "employment_type": "Freelance", "workplace_type": "Remote", "location": "Minneapolis, Minnesota, United States", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interest", "started_on": "2019-01-01", "ended_on": "2019-11-01", "is_current": false, "duration_months": 10, "duration_text": "11 mos", "skills": [ "Front-End Development" ] }, { "ordinal": 16, "position": "Frontend Web Developer", "company_name": "DataFox", "company_linkedin_url": "https://www.linkedin.com/company/datafox/", "employment_type": "Contract", "workplace_type": "Remote", "location": "San Francisco Bay Area", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple a", "started_on": "2017-02-01", "ended_on": "2019-08-01", "is_current": false, "duration_months": 30, "duration_text": "2 yrs 7 mos", "skills": [ "Front-End Development" ] }, { "ordinal": 17, "position": "Wordpress Developer", "company_name": "Compettia - Atrivity", "company_linkedin_url": "https://www.linkedin.com/company/compettia/", "employment_type": "Part-time", "workplace_type": null, "location": "Barcelona, Catalonia, Spain", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as t", "started_on": "2017-01-01", "ended_on": "2019-08-01", "is_current": false, "duration_months": 31, "duration_text": "2 yrs 8 mos", "skills": [] }, { "ordinal": 18, "position": "Full Stack Developer", "company_name": "Cirkus: Scheduling & Project Management", "company_linkedin_url": "https://www.linkedin.com/company/cirkusapp/", "employment_type": "Part-time", "workplace_type": "Remote", "location": "Palma de Mallorca. Spain", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they gr", "started_on": "2018-03-01", "ended_on": "2019-04-01", "is_current": false, "duration_months": 13, "duration_text": "1 yr 2 mos", "skills": [ "On-Page Optimization", "Visual Web Developer", "Google Analytics", "Mixpanel", "HubSpot", "WordPress" ] }, { "ordinal": 19, "position": "Wordpress Developer", "company_name": "Harness", "company_linkedin_url": "https://www.linkedin.com/company/harnessinc/", "employment_type": "Freelance", "workplace_type": "Remote", "location": null, "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that ", "started_on": "2017-11-01", "ended_on": "2018-11-01", "is_current": false, "duration_months": 12, "duration_text": "1 yr 1 mo", "skills": [ "Visual Web Developer" ] }, { "ordinal": 20, "position": "Senior Fullstack Developer", "company_name": "Mammoth Hunters", "company_linkedin_url": "https://www.linkedin.com/search/results/all/?keywords=Mammoth+Hunters", "employment_type": "Full-time", "workplace_type": "On-site", "location": "Barcelona, Catalonia, Spain", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as the", "started_on": "2016-04-01", "ended_on": "2017-08-01", "is_current": false, "duration_months": 16, "duration_text": "1 yr 5 mos", "skills": [ "Visual Web Developer" ] }, { "ordinal": 21, "position": "Frontend Developer", "company_name": "HeadsUP! , Inc", "company_linkedin_url": "https://www.linkedin.com/search/results/all/?keywords=HeadsUP%21+%2C+Inc", "employment_type": "Freelance", "workplace_type": null, "location": "Seattle, Washington, United States", "description": "Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as they grow. Working across product and engineering teams to ship things people use. Interested in systems that stay simple as t", "started_on": "2015-12-01", "ended_on": "2016-10-01", "is_current": false, "duration_months": 10, "duration_text": "11 mos", "skills": [] }, { "ordinal": 22, "position": "IT Staff / Software Developer", "company_name": "Costa Blanca, Ltd.", "company_linkedin_url": "https://www.linkedin.com/search/results/all/?keywords=Costa+Blanca%2C+Ltd%2E", "employment_type": "Full-time", "workplace_type": null, "location": "Russia", "description": "Working across product and engineering teams to ship things people u", "started_on": "2006-01-01", "ended_on": "2014-12-01", "is_current": false, "duration_months": 107, "duration_text": "8 yrs", "skills": [] }, { "ordinal": 23, "position": "IT Staff / Software Developer", "company_name": "Costa Blanca, Ltd.", "company_linkedin_url": "https://www.linkedin.com/search/results/all/?keywords=Costa+Blanca%2C+Ltd%2E", "employment_type": "Internship", "workplace_type": null, "location": "Russia", "description": "Working across product and engineering teams to ship things people u", "started_on": "2003-01-01", "ended_on": "2006-12-01", "is_current": false, "duration_months": 47, "duration_text": "3 yrs", "skills": [] } ], "scraped_at": "2026-09-08T15:41:07.825000Z" } ``` # API contract snapshot Source revision: cb7d67b955f29b059e78da408cdaec0556a46dbd. Production servers URL pending. ```json { "openapi": "3.1.0", "info": { "title": "pineapi", "description": "Pay-as-you-go LinkedIn profile extraction.", "version": "0.1.0" }, "paths": { "/v1/auth/signup": { "post": { "tags": [ "account" ], "summary": "Signup", "operationId": "signup_v1_auth_signup_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SignupRequest" } } }, "required": true }, "responses": { "201": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SessionOut" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } }, "security": [] } }, "/v1/auth/login": { "post": { "tags": [ "account" ], "summary": "Login", "operationId": "login_v1_auth_login_post", "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LoginRequest" } } }, "required": true }, "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SessionOut" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } }, "security": [] } }, "/v1/auth/logout": { "post": { "tags": [ "account" ], "summary": "Logout", "description": "Ends the session that made this request, and only that one.\n\nSigning out of one browser must not sign you out of the others.", "operationId": "logout_v1_auth_logout_post", "parameters": [ { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "204": { "description": "Successful Response" }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/me": { "get": { "tags": [ "account" ], "summary": "Me", "operationId": "me_v1_me_get", "parameters": [ { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MeOut" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/api-keys": { "post": { "tags": [ "account" ], "summary": "Create Key", "description": "Mints a key and shows it once.\n\nReserved to an owner: a key outlives the person and the session that made it,\nso issuing one is an act of delegation and belongs to whoever owns the bill.", "operationId": "create_key_v1_api_keys_post", "parameters": [ { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/KeyRequest" } } } }, "responses": { "201": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreatedKeyOut" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } }, "get": { "tags": [ "account" ], "summary": "List Keys", "description": "Prefixes, never secrets. There is no hash in the response and no way back\nto the key from anything in it.", "operationId": "list_keys_v1_api_keys_get", "parameters": [ { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/KeyOut" }, "title": "Response List Keys V1 Api Keys Get" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/api-keys/{key_id}": { "delete": { "tags": [ "account" ], "summary": "Revoke Key", "description": "Revoking an already-revoked key succeeds.\n\nThe caller asked for it to stop working and it does not work. A key belonging\nto another account is a 404, indistinguishable from one that never existed.", "operationId": "revoke_key_v1_api_keys__key_id__delete", "parameters": [ { "name": "key_id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid", "title": "Key Id" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "204": { "description": "Successful Response" }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/usage": { "get": { "tags": [ "account" ], "summary": "Usage", "description": "The ledger, newest first.\n\n`amount` is the delta applied to the balance, so summing the column\nreconciles against it — a reserve is negative, a refund positive.", "operationId": "usage_v1_usage_get", "parameters": [ { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 500, "minimum": 1, "default": 100, "title": "Limit" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/UsageEntry" }, "title": "Response Usage V1 Usage Get" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/profile-searches": { "post": { "tags": [ "runs" ], "summary": "Create Profile Search", "operationId": "create_profile_search_v1_profile_searches_post", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Idempotency-Key" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProfileSearchRequest" } } } }, "responses": { "202": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RunAccepted" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/profile-lookups": { "post": { "tags": [ "runs" ], "summary": "Create Profile Lookup", "operationId": "create_profile_lookup_v1_profile_lookups_post", "parameters": [ { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Idempotency-Key" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProfileLookupRequest" } } } }, "responses": { "202": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RunAccepted" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/runs/{run_id}": { "get": { "tags": [ "runs" ], "summary": "Get Run", "operationId": "get_run_v1_runs__run_id__get", "parameters": [ { "name": "run_id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid", "title": "Run Id" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RunDetail" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/runs/{run_id}/cancel": { "post": { "tags": [ "runs" ], "summary": "Cancel Run", "description": "Stop a run that has not finished.\n\nCancelling releases the whole reservation: the run delivered nothing, so it\nbills nothing.\n\nCancelling an already-cancelled run is not an error — a client retrying a\nrequest whose response it never saw wants the run's state, not a failure.\nCancelling a *finished* one is, because that asks us to undo something that\nalready cost money, and answering 200 would say we had.", "operationId": "cancel_run_v1_runs__run_id__cancel_post", "parameters": [ { "name": "run_id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid", "title": "Run Id" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RunDetail" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/runs/{run_id}/profiles": { "get": { "tags": [ "runs" ], "summary": "List Profiles", "operationId": "list_profiles_v1_runs__run_id__profiles_get", "parameters": [ { "name": "run_id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid", "title": "Run Id" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 200, "minimum": 1, "default": 50, "title": "Limit" } }, { "name": "cursor", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Cursor" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProfilePage" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/runs/{run_id}/profiles.csv": { "get": { "tags": [ "runs" ], "summary": "Profiles Csv", "operationId": "profiles_csv_v1_runs__run_id__profiles_csv_get", "parameters": [ { "name": "run_id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid", "title": "Run Id" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response" }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/catalogs/locations": { "get": { "tags": [ "catalogs" ], "summary": "Locations", "operationId": "locations_v1_catalogs_locations_get", "parameters": [ { "name": "q", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string", "maxLength": 100 }, { "type": "null" } ], "title": "Q" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 500, "minimum": 1, "default": 50, "title": "Limit" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Catalog" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/catalogs/industries": { "get": { "tags": [ "catalogs" ], "summary": "Industries", "operationId": "industries_v1_catalogs_industries_get", "parameters": [ { "name": "q", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string", "maxLength": 100 }, { "type": "null" } ], "title": "Q" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 500, "minimum": 1, "default": 50, "title": "Limit" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Catalog" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/catalogs/schools": { "get": { "tags": [ "catalogs" ], "summary": "Schools", "description": "Suggestions, not an allowlist.\n\nUnlike locations this list is a curated selection rather than validated\ncoverage, so `schools` accepts values that are not in it. Rejecting every\nschool outside it would refuse most legitimate searches.", "operationId": "schools_v1_catalogs_schools_get", "parameters": [ { "name": "q", "in": "query", "required": false, "schema": { "anyOf": [ { "type": "string", "maxLength": 100 }, { "type": "null" } ], "title": "Q" } }, { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "maximum": 500, "minimum": 1, "default": 50, "title": "Limit" } }, { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Catalog" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/catalogs/company-headcounts": { "get": { "tags": [ "catalogs" ], "summary": "Company Headcounts", "operationId": "company_headcounts_v1_catalogs_company_headcounts_get", "parameters": [ { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Catalog" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/catalogs/experience-bands": { "get": { "tags": [ "catalogs" ], "summary": "Experience Bands", "operationId": "experience_bands_v1_catalogs_experience_bands_get", "parameters": [ { "name": "authorization", "in": "header", "required": false, "schema": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Authorization" } } ], "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Catalog" } } } }, "422": { "description": "Validation Error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HTTPValidationError" } } } } } } }, "/v1/demo/profile": { "get": { "tags": [ "demo" ], "summary": "Demo Profile", "operationId": "demo_profile_v1_demo_profile_get", "responses": { "200": { "description": "Successful Response", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProfilePage" } } } } }, "security": [] } } }, "components": { "schemas": { "AccountOut": { "properties": { "account_id": { "type": "string", "format": "uuid", "title": "Account Id" }, "name": { "type": "string", "title": "Name" }, "role": { "type": "string", "title": "Role" }, "balance": { "$ref": "#/components/schemas/Money" }, "status": { "type": "string", "title": "Status" } }, "type": "object", "required": [ "account_id", "name", "role", "balance", "status" ], "title": "AccountOut" }, "Catalog": { "properties": { "data": { "items": { "$ref": "#/components/schemas/CatalogEntry" }, "type": "array", "title": "Data" }, "total": { "type": "integer", "title": "Total" }, "max_per_request": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Max Per Request" } }, "type": "object", "required": [ "data", "total" ], "title": "Catalog" }, "CatalogEntry": { "properties": { "value": { "type": "string", "title": "Value" }, "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Label" } }, "type": "object", "required": [ "value" ], "title": "CatalogEntry" }, "CreatedKeyOut": { "properties": { "id": { "type": "string", "format": "uuid", "title": "Id" }, "name": { "type": "string", "title": "Name" }, "key_prefix": { "type": "string", "title": "Key Prefix" }, "scopes": { "items": { "type": "string" }, "type": "array", "title": "Scopes" }, "created_at": { "type": "string", "format": "date-time", "title": "Created At" }, "last_used_at": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ], "title": "Last Used At" }, "revoked_at": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ], "title": "Revoked At" }, "created_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Created By" }, "secret": { "type": "string", "title": "Secret" } }, "type": "object", "required": [ "id", "name", "key_prefix", "scopes", "created_at", "secret" ], "title": "CreatedKeyOut", "description": "Carries the secret, which is why it is a separate type.\n\nReturned exactly once. Only a hash is stored, so a lost key is replaced\nrather than recovered." }, "Experience": { "properties": { "ordinal": { "type": "integer", "title": "Ordinal" }, "position": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Position" }, "company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Company Name" }, "company_linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Company Linkedin Url" }, "employment_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Employment Type" }, "workplace_type": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Workplace Type" }, "location": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Location" }, "description": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Description" }, "started_on": { "anyOf": [ { "type": "string", "format": "date" }, { "type": "null" } ], "title": "Started On" }, "ended_on": { "anyOf": [ { "type": "string", "format": "date" }, { "type": "null" } ], "title": "Ended On" }, "is_current": { "type": "boolean", "title": "Is Current", "default": false }, "duration_months": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Duration Months" }, "duration_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Duration Text" }, "skills": { "items": { "type": "string" }, "type": "array", "title": "Skills", "default": [] } }, "type": "object", "required": [ "ordinal" ], "title": "Experience" }, "Failure": { "properties": { "reason": { "type": "string", "title": "Reason" }, "detail": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Detail" } }, "type": "object", "required": [ "reason" ], "title": "Failure" }, "HTTPValidationError": { "properties": { "detail": { "items": { "$ref": "#/components/schemas/ValidationError" }, "type": "array", "title": "Detail" } }, "type": "object", "title": "HTTPValidationError" }, "KeyOut": { "properties": { "id": { "type": "string", "format": "uuid", "title": "Id" }, "name": { "type": "string", "title": "Name" }, "key_prefix": { "type": "string", "title": "Key Prefix" }, "scopes": { "items": { "type": "string" }, "type": "array", "title": "Scopes" }, "created_at": { "type": "string", "format": "date-time", "title": "Created At" }, "last_used_at": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ], "title": "Last Used At" }, "revoked_at": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ], "title": "Revoked At" }, "created_by": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Created By" } }, "type": "object", "required": [ "id", "name", "key_prefix", "scopes", "created_at" ], "title": "KeyOut" }, "KeyRequest": { "properties": { "name": { "type": "string", "maxLength": 200, "minLength": 1, "title": "Name" }, "scopes": { "items": { "type": "string" }, "type": "array", "title": "Scopes" } }, "type": "object", "required": [ "name" ], "title": "KeyRequest" }, "LoginRequest": { "properties": { "email": { "type": "string", "maxLength": 254, "title": "Email" }, "password": { "type": "string", "maxLength": 1024, "minLength": 1, "title": "Password" } }, "type": "object", "required": [ "email", "password" ], "title": "LoginRequest" }, "MeOut": { "properties": { "user_id": { "type": "string", "format": "uuid", "title": "User Id" }, "accounts": { "items": { "$ref": "#/components/schemas/AccountOut" }, "type": "array", "title": "Accounts" } }, "type": "object", "required": [ "user_id", "accounts" ], "title": "MeOut" }, "Money": { "properties": { "micros": { "type": "integer", "title": "Micros" }, "usd": { "type": "string", "title": "Usd" }, "currency": { "type": "string", "title": "Currency", "default": "USD" } }, "type": "object", "required": [ "micros", "usd" ], "title": "Money", "description": "Exact, and legible.\n\n`micros` is the number to do arithmetic with — money in a float is how\nrounding errors become invoices. `usd` is for the human reading the terminal." }, "Pagination": { "properties": { "total_elements": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Total Elements" }, "total_pages": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Total Pages" }, "page_number": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Page Number" }, "page_size": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Page Size" } }, "type": "object", "title": "Pagination", "description": "The provider's own view of the result set this run drew from.\n\n`total_pages` is what makes an out-of-range `start_page` refusable. Note\nthat these numbers drift between calls: repeated runs of one search have\nbeen measured declaring totals 1.21x apart on average." }, "ProfileLookupRequest": { "properties": { "linkedin_urls": { "items": { "type": "string", "maxLength": 500 }, "type": "array", "maxItems": 2000, "minItems": 1, "title": "Linkedin Urls" }, "label": { "anyOf": [ { "type": "string", "maxLength": 200 }, { "type": "null" } ], "title": "Label" }, "webhook_url": { "anyOf": [ { "type": "string", "maxLength": 2000 }, { "type": "null" } ], "title": "Webhook Url" } }, "additionalProperties": false, "type": "object", "required": [ "linkedin_urls" ], "title": "ProfileLookupRequest", "description": "Profiles addressed directly. No pages, no block of 25, no billing floor." }, "ProfileOut": { "properties": { "id": { "type": "string", "format": "uuid", "title": "Id" }, "ordinal": { "type": "integer", "title": "Ordinal" }, "public_identifier": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Public Identifier" }, "linkedin_url": { "type": "string", "title": "Linkedin Url" }, "requested_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Requested Url" }, "first_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "First Name" }, "last_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Last Name" }, "full_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Full Name" }, "headline": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Headline" }, "about": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "About" }, "location_text": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Location Text" }, "location_city": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Location City" }, "location_state": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Location State" }, "location_region_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Location Region Code" }, "location_country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Location Country" }, "location_country_full": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Location Country Full" }, "location_country_code": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Location Country Code" }, "open_to_work": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Open To Work" }, "hiring": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Hiring" }, "premium": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Premium" }, "influencer": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Influencer" }, "creator": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Creator" }, "verified": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Verified" }, "open_profile": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Open Profile" }, "memorialized": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Memorialized" }, "connections_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Connections Count" }, "follower_count": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Follower Count" }, "registered_at": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ], "title": "Registered At" }, "profile_language": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Profile Language" }, "profile_country": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Profile Country" }, "profile_picture_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Profile Picture Url" }, "photo_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Photo Url" }, "cover_picture_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Cover Picture Url" }, "current_title": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Current Title" }, "current_company_name": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Current Company Name" }, "current_company_linkedin_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Current Company Linkedin Url" }, "current_position_started_on": { "anyOf": [ { "type": "string", "format": "date" }, { "type": "null" } ], "title": "Current Position Started On" }, "total_months_experience": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Total Months Experience" }, "months_at_current_company": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Months At Current Company" }, "months_in_current_role": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Months In Current Role" }, "promoted_at_current_company": { "type": "boolean", "title": "Promoted At Current Company", "default": false }, "promotions_count": { "type": "integer", "title": "Promotions Count", "default": 0 }, "companies_count": { "type": "integer", "title": "Companies Count", "default": 0 }, "avg_months_per_company": { "anyOf": [ { "type": "integer" }, { "type": "null" } ], "title": "Avg Months Per Company" }, "linkedin_member_since": { "anyOf": [ { "type": "string", "format": "date" }, { "type": "null" } ], "title": "Linkedin Member Since" }, "skills_flat": { "items": { "type": "string" }, "type": "array", "title": "Skills Flat", "default": [] }, "languages_flat": { "items": { "type": "string" }, "type": "array", "title": "Languages Flat", "default": [] }, "school_names": { "items": { "type": "string" }, "type": "array", "title": "School Names", "default": [] }, "company_names": { "items": { "type": "string" }, "type": "array", "title": "Company Names", "default": [] }, "section_totals": { "anyOf": [ { "additionalProperties": true, "type": "object" }, { "type": "null" } ], "title": "Section Totals" }, "experience_truncated": { "type": "boolean", "title": "Experience Truncated", "default": false }, "skills_truncated": { "type": "boolean", "title": "Skills Truncated", "default": false }, "education": { "items": {}, "type": "array", "title": "Education", "default": [] }, "certifications": { "items": {}, "type": "array", "title": "Certifications", "default": [] }, "languages": { "items": {}, "type": "array", "title": "Languages", "default": [] }, "skills": { "items": {}, "type": "array", "title": "Skills", "default": [] }, "projects": { "items": {}, "type": "array", "title": "Projects", "default": [] }, "publications": { "items": {}, "type": "array", "title": "Publications", "default": [] }, "courses": { "items": {}, "type": "array", "title": "Courses", "default": [] }, "honors": { "items": {}, "type": "array", "title": "Honors", "default": [] }, "volunteering": { "items": {}, "type": "array", "title": "Volunteering", "default": [] }, "recommendations": { "items": {}, "type": "array", "title": "Recommendations", "default": [] }, "interests": { "items": {}, "type": "array", "title": "Interests", "default": [] }, "patents": { "items": {}, "type": "array", "title": "Patents", "default": [] }, "organizations": { "items": {}, "type": "array", "title": "Organizations", "default": [] }, "websites": { "items": {}, "type": "array", "title": "Websites", "default": [] }, "top_skills": { "items": {}, "type": "array", "title": "Top Skills", "default": [] }, "experiences": { "items": { "$ref": "#/components/schemas/Experience" }, "type": "array", "title": "Experiences", "default": [] }, "scraped_at": { "type": "string", "format": "date-time", "title": "Scraped At" } }, "type": "object", "required": [ "id", "ordinal", "linkedin_url", "scraped_at" ], "title": "ProfileOut" }, "ProfilePage": { "properties": { "data": { "items": { "$ref": "#/components/schemas/ProfileOut" }, "type": "array", "title": "Data" }, "has_more": { "type": "boolean", "title": "Has More" }, "next_cursor": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Next Cursor" } }, "type": "object", "required": [ "data", "has_more" ], "title": "ProfilePage" }, "ProfileSearchRequest": { "properties": { "search_query": { "anyOf": [ { "type": "string", "maxLength": 300 }, { "type": "null" } ], "title": "Search Query" }, "locations": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Locations" }, "exclude_locations": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Locations" }, "current_job_titles": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Current Job Titles" }, "past_job_titles": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Past Job Titles" }, "exclude_current_job_titles": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Current Job Titles" }, "exclude_past_job_titles": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Past Job Titles" }, "current_companies": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Current Companies" }, "past_companies": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Past Companies" }, "exclude_current_companies": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Current Companies" }, "exclude_past_companies": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Past Companies" }, "schools": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Schools" }, "exclude_schools": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Schools" }, "industry_ids": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Industry Ids" }, "exclude_industry_ids": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Industry Ids" }, "function_ids": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Function Ids" }, "exclude_function_ids": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Function Ids" }, "seniority_ids": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Seniority Ids" }, "exclude_seniority_ids": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Seniority Ids" }, "years_of_experience": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Years Of Experience" }, "years_at_current_company": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Years At Current Company" }, "company_headcount": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Company Headcount" }, "company_hq_locations": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Company Hq Locations" }, "exclude_company_hq_locations": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Exclude Company Hq Locations" }, "first_names": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "First Names" }, "last_names": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Last Names" }, "profile_languages": { "items": { "type": "string", "maxLength": 200 }, "type": "array", "maxItems": 100, "title": "Profile Languages" }, "recently_changed_jobs": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Recently Changed Jobs" }, "recently_posted": { "anyOf": [ { "type": "boolean" }, { "type": "null" } ], "title": "Recently Posted" }, "max_items": { "type": "integer", "maximum": 2000, "minimum": 25, "title": "Max Items", "description": "Profiles to fetch. A multiple of 25: the search is served and billed in blocks of 25 whether or not the whole block is taken, so asking for 30 would pay for 50.", "default": 100 }, "start_page": { "type": "integer", "maximum": 10000, "minimum": 1, "title": "Start Page", "description": "Which page of the result set to start from. NOT a stable cursor: the underlying ordering shifts between calls, so consecutive pages can overlap or skip. Prefer one call with a high max_items over several with paging.", "default": 1 }, "label": { "anyOf": [ { "type": "string", "maxLength": 200 }, { "type": "null" } ], "title": "Label" }, "webhook_url": { "anyOf": [ { "type": "string", "maxLength": 2000 }, { "type": "null" } ], "title": "Webhook Url" } }, "additionalProperties": false, "type": "object", "title": "ProfileSearchRequest", "description": "Filters inline, plus the two knobs that control size and position." }, "RunAccepted": { "properties": { "run_id": { "type": "string", "format": "uuid", "title": "Run Id" }, "status": { "type": "string", "title": "Status" }, "mode": { "type": "string", "title": "Mode" }, "resource_type": { "type": "string", "title": "Resource Type" }, "start_page": { "type": "integer", "title": "Start Page" }, "max_items": { "type": "integer", "title": "Max Items" }, "webhook_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Webhook Url" }, "created_at": { "type": "string", "format": "date-time", "title": "Created At" } }, "type": "object", "required": [ "run_id", "status", "mode", "resource_type", "start_page", "max_items", "webhook_url", "created_at" ], "title": "RunAccepted", "description": "What a 202 carries. The run is queued, not done." }, "RunDetail": { "properties": { "run_id": { "type": "string", "format": "uuid", "title": "Run Id" }, "status": { "type": "string", "title": "Status" }, "mode": { "type": "string", "title": "Mode" }, "resource_type": { "type": "string", "title": "Resource Type" }, "label": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Label" }, "input": { "additionalProperties": true, "type": "object", "title": "Input" }, "start_page": { "type": "integer", "title": "Start Page" }, "max_items": { "type": "integer", "title": "Max Items" }, "profiles_delivered": { "type": "integer", "title": "Profiles Delivered" }, "duplicates_discarded": { "type": "integer", "title": "Duplicates Discarded" }, "charged": { "$ref": "#/components/schemas/Money" }, "pagination": { "$ref": "#/components/schemas/Pagination" }, "failure": { "anyOf": [ { "$ref": "#/components/schemas/Failure" }, { "type": "null" } ] }, "unresolved": { "items": { "$ref": "#/components/schemas/Unresolved" }, "type": "array", "title": "Unresolved", "default": [] }, "created_at": { "type": "string", "format": "date-time", "title": "Created At" }, "started_at": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ], "title": "Started At" }, "finished_at": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ], "title": "Finished At" }, "expires_at": { "type": "string", "format": "date-time", "title": "Expires At" } }, "type": "object", "required": [ "run_id", "status", "mode", "resource_type", "input", "start_page", "max_items", "profiles_delivered", "duplicates_discarded", "charged", "pagination", "created_at", "expires_at" ], "title": "RunDetail" }, "SessionOut": { "properties": { "session_token": { "type": "string", "title": "Session Token" }, "expires_at": { "type": "string", "format": "date-time", "title": "Expires At" }, "account_id": { "type": "string", "format": "uuid", "title": "Account Id" }, "role": { "type": "string", "title": "Role" }, "scopes": { "items": { "type": "string" }, "type": "array", "title": "Scopes" } }, "type": "object", "required": [ "session_token", "expires_at", "account_id", "role", "scopes" ], "title": "SessionOut", "description": "The token is shown once and is not retrievable afterwards.\n\nSent back as `Authorization: Bearer sess_...`. Deliberately not a cookie: a\ncookie travels automatically, which is what makes CSRF possible at all, and a\nbearer credential does not. See app/sessions.py." }, "SignupRequest": { "properties": { "email": { "type": "string", "maxLength": 254, "title": "Email" }, "password": { "type": "string", "maxLength": 1024, "minLength": 12, "title": "Password" }, "account_name": { "type": "string", "maxLength": 200, "minLength": 1, "title": "Account Name" } }, "type": "object", "required": [ "email", "password", "account_name" ], "title": "SignupRequest" }, "Unresolved": { "properties": { "requested_url": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Requested Url" }, "reason": { "type": "string", "title": "Reason" }, "detail": { "anyOf": [ { "type": "string" }, { "type": "null" } ], "title": "Detail" } }, "type": "object", "required": [ "reason" ], "title": "Unresolved", "description": "A URL that was asked for and did not come back as a profile.\n\nLookup mode only. The caller sent a list and is owed an answer about every\nitem in it: a batch of 200 that returns 187 profiles has to say which 13\ndid not resolve, and `requested_url` is how it says so. None of these are\nbilled." }, "UsageEntry": { "properties": { "kind": { "type": "string", "title": "Kind" }, "units": { "type": "integer", "title": "Units" }, "amount": { "$ref": "#/components/schemas/Money" }, "run_id": { "anyOf": [ { "type": "string", "format": "uuid" }, { "type": "null" } ], "title": "Run Id" }, "created_at": { "type": "string", "format": "date-time", "title": "Created At" } }, "type": "object", "required": [ "kind", "units", "amount", "created_at" ], "title": "UsageEntry" }, "ValidationError": { "properties": { "loc": { "items": { "anyOf": [ { "type": "string" }, { "type": "integer" } ] }, "type": "array", "title": "Location" }, "msg": { "type": "string", "title": "Message" }, "type": { "type": "string", "title": "Error Type" }, "input": { "title": "Input" }, "ctx": { "type": "object", "title": "Context" } }, "type": "object", "required": [ "loc", "msg", "type" ], "title": "ValidationError" } }, "securitySchemes": { "bearerAuth": { "type": "http", "scheme": "bearer", "description": "An API key (`pine_sk_…`) for machines, or a session token (`sess_…`) for a signed-in person. Both go in `Authorization: Bearer …`." } } }, "security": [ { "bearerAuth": [] } ] } ``` --- # LinkedIn profile data, ready for your code > Search LinkedIn by filter and get structured fields per profile. $0.012 per profile delivered, $0.12 minimum per search, no subscription. URL: https://pineapi.dev Last updated: 2026-10-19 pineapi searches LinkedIn by filter and returns every match as 53 structured fields. No LinkedIn account, no proxies and no session cookies, with asynchronous search and URL lookup through one API. ## LinkedIn scraper API for developers pineapi supports two inputs: search filters to discover profiles, or profile URLs to look up people you already know. Both use the same prepaid balance. - Set filters, get every matching profile as JSON. - 53 fields per profile plus 16 nested collections, versioned under /v1/. - No LinkedIn account, no session cookie, no proxies to rotate. ## Use your AI assistant - Give Claude, ChatGPT or Cursor a compact API integration brief. - Use an assistant with HTTP tools to search or look up URLs. - Includes spending checks, polling, pagination and error handling. The API supports filter searches and lookup by profile URL. Both return the same 53 fields plus 16 nested collections. URL lookup has no search minimum. ## How scraping LinkedIn profiles works here pineapi uses an asynchronous workflow: create a run, poll its status, then download the results. 1. **Describe who you are looking for**: Set the filters: titles, locations, industries, schools, company headcount, seniority, years of experience. Then say how many profiles you want back, from 25 to 2,000 in blocks of 25. 2. **Get the matching profiles**: pineapi runs the search and returns every match as 53 named fields plus 16 nested collections. Searches take minutes; lookups usually take seconds. These are not completion guarantees. 3. **Pay for the profiles delivered**: $0.012 per profile that comes back, with a $0.12 minimum per search. A search that fails is not billed at all. ## Four things you can check yourself pineapi claims these and nothing beyond them. Each one is verifiable from your own side of the API on the first day you use it, which is the only kind of claim worth making in a category where every vendor says the same adjectives. - **One price, one line on the invoice**: No compute units, no proxy fees, no actor cost and no per-GB egress to reconcile. You are quoted per profile and billed per profile. - **No infrastructure to run**: No proxies to rotate, no browser to keep alive, no LinkedIn account, no session cookie and no browser extension. - **Two modes, one schema, one balance**: Search by filters or look up profile URLs. Both modes return the same profile schema and draw from the same prepaid balance. - **A published, stable response schema**: Profile fields and nested collections generated from the OpenAPI contract, versioned under /v1/. ## What you get back for every profile pineapi returns 53 top-level fields per profile plus 16 nested collections, listed here in full. Dates are ISO dates rather than "Dec 2025" strings, endorsement counts are integers rather than "22 endorsements", and is_current is a boolean rather than the word "Present". **Profile fields**: Generated from the API OpenAPI schema. Nullable values can be absent from the source profile. - `id` (string (uuid)): Id - `ordinal` (integer): Ordinal - `public_identifier` (string | null): Public Identifier - `linkedin_url` (string): Linkedin Url - `requested_url` (string | null): Requested Url - `first_name` (string | null): First Name - `last_name` (string | null): Last Name - `full_name` (string | null): Full Name - `headline` (string | null): Headline - `about` (string | null): About - `location_text` (string | null): Location Text - `location_city` (string | null): Location City - `location_state` (string | null): Location State - `location_region_code` (string | null): Location Region Code - `location_country` (string | null): Location Country - `location_country_full` (string | null): Location Country Full - `location_country_code` (string | null): Location Country Code - `open_to_work` (boolean | null): Open To Work - `hiring` (boolean | null): Hiring - `premium` (boolean | null): Premium - `influencer` (boolean | null): Influencer - `creator` (boolean | null): Creator - `verified` (boolean | null): Verified - `open_profile` (boolean | null): Open Profile - `memorialized` (boolean | null): Memorialized - `connections_count` (integer | null): Connections Count - `follower_count` (integer | null): Follower Count - `registered_at` (string (date-time) | null): Registered At - `profile_language` (string | null): Profile Language - `profile_country` (string | null): Profile Country - `profile_picture_url` (string | null): Profile Picture Url - `photo_url` (string | null): Photo Url - `cover_picture_url` (string | null): Cover Picture Url - `current_title` (string | null): Current Title - `current_company_name` (string | null): Current Company Name - `current_company_linkedin_url` (string | null): Current Company Linkedin Url - `current_position_started_on` (string (date) | null): Current Position Started On - `total_months_experience` (integer | null): Total Months Experience - `months_at_current_company` (integer | null): Months At Current Company - `months_in_current_role` (integer | null): Months In Current Role - `promoted_at_current_company` (boolean): Promoted At Current Company - `promotions_count` (integer): Promotions Count - `companies_count` (integer): Companies Count - `avg_months_per_company` (integer | null): Avg Months Per Company - `linkedin_member_since` (string (date) | null): Linkedin Member Since - `skills_flat` (string[]): Skills Flat - `languages_flat` (string[]): Languages Flat - `school_names` (string[]): School Names - `company_names` (string[]): Company Names - `section_totals` (object | null): Section Totals - `experience_truncated` (boolean): Experience Truncated - `skills_truncated` (boolean): Skills Truncated - `scraped_at` (string (date-time)): Scraped At ### 16 nested collections - `education[]`: Education Sub-fields: . - `certifications[]`: Certifications Sub-fields: . - `languages[]`: Languages Sub-fields: . - `skills[]`: Skills Sub-fields: . - `projects[]`: Projects Sub-fields: . - `publications[]`: Publications Sub-fields: . - `courses[]`: Courses Sub-fields: . - `honors[]`: Honors Sub-fields: . - `volunteering[]`: Volunteering Sub-fields: . - `recommendations[]`: Recommendations Sub-fields: . - `interests[]`: Interests Sub-fields: . - `patents[]`: Patents Sub-fields: . - `organizations[]`: Organizations Sub-fields: . - `websites[]`: Websites Sub-fields: . - `top_skills[]`: Top Skills Sub-fields: . - `experiences[]`: Experiences Sub-fields: ordinal, position, company_name, company_linkedin_url, employment_type, workplace_type, location, description, started_on, ended_on, is_current, duration_months, duration_text, skills. Email addresses are not returned. Field names are versioned under /v1/ and are not renamed, because pipelines and agents cache the shape of a response and a silent rename breaks them quietly. ## Five fields LinkedIn does not publish pineapi computes these from the work history rather than passing LinkedIn's raw shape through, and they are the reason the record is worth more than its source. - **months_at_current_company vs months_in_current_role**: Separate company tenure from role tenure without parsing the work history yourself. - **promoted_at_current_company and promotions_count**: See internal role changes derived from the work history, alongside company and role tenure. - **experience_truncated and skills_truncated**: 40% of profiles come back with truncated work history and 68% with truncated skills. Any years-of-experience figure computed from one of those is an undercount, and you only know it happened if the record tells you. - **Real dates, not display strings**: started_on and ended_on are ISO dates and is_current is a boolean, so tenure arithmetic works without parsing month names. - **endorsement_count as an integer**: Skill endorsements come back as numbers you can sort on, not as the text LinkedIn renders. ## LinkedIn scraping pricing: $0.012 per profile pineapi charges $0.012 for every profile delivered, $12 per thousand, with a $0.12 minimum per search, which is the price of ten profiles. There is no subscription, no seat fee and no monthly minimum, and lookup has no search minimum. Top up from $5; credit does not expire. | Rule | Value | | --- | --- | | Price per profile delivered | $0.012 ($12 per 1,000) | | Minimum per search | $0.12 | | Minimum per lookup | None; unresolved URLs are free | | Minimum top-up | $5; credit does not expire | | Billing unit | Profiles delivered, not profiles requested. | | Duplicates | Duplicates within a run are discarded and not billed. A profile delivered again in a separate run is billed again. | | Failed searches | A search that fails is not billed. | | Empty searches | A search that completes and matches nobody is billed the $0.12 minimum, because the search ran. | | Subscription, seats, monthly minimum | None | A search that fails is not billed. A search that completes and matches nobody is billed the $0.12 minimum, because the search ran. Duplicates within a run are discarded and not billed. A profile delivered again in a separate run is billed again. ## Use pineapi with your AI assistant Copy one brief into Claude, ChatGPT or Cursor. Give your assistant the steps to search, poll and download profiles through the API. [Use with AI →](https://pineapi.dev/use-with-ai) ## Compare pineapi with other LinkedIn data tools pineapi differs from the usual options in how the bill is built and in what each one needs from you. The table compares pricing models and setup requirements rather than a snapshot of rates that would go stale. | | pineapi | Proxycurl | PhantomBuster | Sales Navigator | | --- | --- | --- | --- | --- | | Pricing model | $0.012 per profile delivered, $0.12 minimum per search | Was per credit, service discontinued | Subscription: execution time, slots and credits | Monthly seat per user | | Monthly minimum | None | Plan minimum | Plan minimum | Annual seat commitment | | LinkedIn account or cookie | Not required | Not required | Your session cookie | Your own seat | | Charged when nothing parses | No. A failed search is not billed | n/a | Yes. Runtime is billed | n/a | | CSV export | Available through the API | No | Yes | Export is restricted | ## What people use pineapi for - **Sales prospecting**: Filter by title, seniority, industry and company headcount, and get a prospect list with current role, employer and tenure ready to push into your CRM. - **Recruitment sourcing**: Filter by title, school, years of experience and location, and get a shortlist with experience history and truncation flags, including who has been promoted internally and who has not. - **Data enrichment**: Match people you already have to their profiles and write back title, employer, tenure and location on a schedule. ## What pineapi collects, and what it does not pineapi reads data that LinkedIn shows publicly, without logging into an account and without asking you for a session cookie. You do not provide your own LinkedIn credentials. pineapi does not return email addresses or phone numbers, and is not an email-enrichment service. Profile free text may still contain personal information that requires careful handling. Search results stay downloadable for 30 days and are then deleted. Under GDPR you are the controller for whatever you do with the data you receive: lawful basis, notice and retention are yours to decide. Consult the privacy notice and request the processing agreement for your use case. ## Questions ### How much does it cost to scrape a LinkedIn profile? $0.012 per profile delivered, $12 per thousand, with a $0.12 minimum per search, which is the price of ten profiles. There is no subscription, no seat fee and no monthly minimum. A search that fails is not billed, and duplicates are billed: a profile you already received is billed again if it comes back. ### What if my search returns no profiles? That means no LinkedIn profiles matched your filters, not that the search failed. You are billed the $0.12 search minimum. Broaden a filter, usually location or job title, and run it again. ### Do I need a LinkedIn account or cookies? No. pineapi needs no LinkedIn account, no session cookie, no browser extension and no proxy of your own. You send filters and your API key, and the matching profiles come back. Nothing is tied to a personal login, so no account of yours is exposed by using it. ### What data comes back for each profile? 53 top-level fields plus 16 nested collections: identity, headline and about, parsed location, status badges, follower and connection counts, current role, computed tenure and promotion history, flattened skills and schools, truncation flags, and arrays for experience, education, skills, certifications, languages, projects, publications and more. Email addresses are not returned. ### How long does a search take? Searches take minutes and lookups usually take seconds. Poll every 5 seconds for the first minute, then every 20 seconds. These are integration intervals, not a guaranteed completion time. ### Is scraping LinkedIn legal? It depends on the data, the jurisdiction and what you do next, and nobody can honestly answer it with a yes. Courts in the United States have repeatedly declined to treat scraping public pages as unauthorised access, while GDPR still applies to personal data you collect in Europe. Read the legality page before you build on it. ### Do you have a free trial? No, and there is nothing to cancel either. pineapi is pay as you go: you add balance, you spend it per profile delivered, you stop whenever. Top up by card from $5 before starting paid runs. The anonymised demo is the free sample; there are no test keys. You pay for what you run and nothing else. ### Can I use this without writing code? The product is an API. You can give the integration brief to an assistant with HTTP tools. The dashboard manages keys, balance and run history; a no-code scraping form and bulk CSV upload are not offered. ### How long do you keep my results? 30 days. Search results stay available through the API and as CSV for that window, and re-downloading inside it is free. After 30 days they are deleted, so store anything you need to keep. ### Are duplicate profiles billed twice? Yes. Billing counts profiles delivered, so a profile that appears in two searches is billed in both. LinkedIn re-ranks search results over time, so consecutive paginated calls on the same search overlap by about 30%, so one large search costs less than several small ones covering the same ground. ### Can I scrape Sales Navigator search results? You do not need to. pineapi takes the same kind of filters directly, from title and seniority to industry, headcount and location, and returns the matching profiles as structured data. Keep your Sales Navigator seat for searching if you want it; you stop paying seats to get results out. --- # LinkedIn scraper API > A LinkedIn scraper API that turns filters into structured profiles. Structured JSON, no cookies, $0.012 per profile delivered. Read the full schema. URL: https://pineapi.dev/linkedin-scraper-api Last updated: 2026-10-19 pineapi is a LinkedIn scraper API that takes a set of filters (job title, location, industry, company headcount, seniority, school, years of experience and about thirty more) and returns every matching profile as 53 structured fields plus 16 nested collections. $0.012 per profile delivered, $0.12 minimum per search. No LinkedIn account, no session cookie and no proxies of your own: authentication is a bearer token, and a search that fails is not billed. ## Start a search, then read the results pineapi runs a search as a job because a real search takes minutes, not milliseconds: poll every 5 seconds for the first minute, then every 20 seconds. You start one, then read it when it is done. ```bash # 1. Create a run. Keep this key and body unchanged for retries. IDEMPOTENCY_KEY=$(uuidgen) curl --fail-with-body $PINEAPI_BASE_URL/v1/profile-searches \ -H "Authorization: Bearer $PINEAPI_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H "Content-Type: application/json" \ -d '{"current_job_titles":["Head of Talent"],"locations":["Spain"],"max_items":100}' # HTTP 202: { "run_id": "", "status": "queued", ... } # 2. Copy run_id from that response. Poll until succeeded or partial. RUN_ID='' curl --fail-with-body "$PINEAPI_BASE_URL/v1/runs/$RUN_ID" \ -H "Authorization: Bearer $PINEAPI_KEY" # queued / starting / running / processing are in progress. # failed / cancelled are terminal: inspect failure before retrying. # 3. Download profiles after success. Follow next_cursor while has_more is true. curl --fail-with-body "$PINEAPI_BASE_URL/v1/runs/$RUN_ID/profiles?limit=200" \ -H "Authorization: Bearer $PINEAPI_KEY" # { "data": [...], "has_more": false, "next_cursor": null } ``` ```js import { randomUUID } from 'node:crypto'; const base = process.env.PINEAPI_BASE_URL + '/v1'; const auth = { Authorization: `Bearer ${process.env.PINEAPI_KEY}` }; async function json(path, options = {}) { const response = await fetch(base + path, { ...options, headers: { ...auth, ...options.headers }, signal: AbortSignal.timeout(30_000) }); if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`); return response.json(); } // Persist this key and body if your application retries the creation request. const key = randomUUID(); const { run_id } = await json('/profile-searches', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Idempotency-Key': key }, body: JSON.stringify({ current_job_titles: ['Head of Talent'], locations: ['Spain'], max_items: 100 }), }); const pending = new Set(['queued', 'starting', 'running', 'processing']); const started = Date.now(); const deadline = Date.now() + 30 * 60_000; let run; do { if (Date.now() > deadline) throw new Error(`Polling timeout; resume with run_id ${run_id}`); await new Promise(resolve => setTimeout(resolve, Date.now() - started < 60_000 ? 5_000 : 20_000)); run = await json(`/runs/${run_id}`); } while (pending.has(run.status)); if (!['succeeded', 'partial'].includes(run.status)) throw new Error(JSON.stringify(run.failure ?? run.status)); let cursor; do { const query = new URLSearchParams({ limit: '200' }); if (cursor) query.set('cursor', cursor); const page = await json(`/runs/${run_id}/profiles?${query}`); for (const profile of page.data) console.log(profile.full_name); cursor = page.has_more ? page.next_cursor : null; } while (cursor); ``` ```python import os, time, uuid, requests base = os.environ["PINEAPI_BASE_URL"].rstrip("/") + "/v1" session = requests.Session() session.headers["Authorization"] = f"Bearer {os.environ['PINEAPI_KEY']}" def get(path, **kwargs): response = session.get(base + path, timeout=30, **kwargs) response.raise_for_status() return response.json() # Keep this key and the same body if retrying the creation request. key = str(uuid.uuid4()) response = session.post(base + "/profile-searches", timeout=30, headers={"Idempotency-Key": key}, json={"current_job_titles": ["Head of Talent"], "locations": ["Spain"], "max_items": 100}) response.raise_for_status() run_id = response.json()["run_id"] started = time.monotonic() deadline = started + 1800 while True: if time.monotonic() > deadline: raise TimeoutError(f"Resume polling run_id {run_id}") run = get(f"/runs/{run_id}") if run["status"] not in {"queued", "starting", "running", "processing"}: break time.sleep(5 if time.monotonic() - started < 60 else 20) if run["status"] not in {"succeeded", "partial"}: raise RuntimeError(run.get("failure") or run["status"]) params = {"limit": 200} while True: page = get(f"/runs/{run_id}/profiles", params=params) for profile in page["data"]: print(profile["full_name"]) if not page["has_more"]: break params["cursor"] = page["next_cursor"] ``` Authentication is a bearer token and nothing else. pineapi runs no OAuth handshake, because an agent that has to complete a browser redirect before its first call is an agent that gives up and picks something else. Creation requires an Idempotency-Key header of up to 255 characters. Reuse the key and unchanged request on retries. Optional webhook_url must be a public HTTPS URL: it receives a signed event containing data.run metadata, not profiles. Customer access to the webhook signing secret is not yet implemented; use authenticated polling as the source of truth. Fetch profiles separately and follow next_cursor while has_more is true. Respect Retry-After on HTTP 429; the examples stop on HTTP errors so your application can apply its retry policy. ## Two ways to ask for profiles pineapi implements two kinds of input and returns the identical 53-field response for both. - **Search by filters**: Describe who you are looking for: job titles, locations, industries, schools, company headcount, seniority, years of experience and around thirty more. Every matching profile comes back. - **Lookup by profile URL**: POST /v1/profile-lookups accepts linkedin_urls with 1 to 2,000 profile URLs. URLs are normalized and deduplicated within the request. Same response schema and per-profile price; no search minimum or block-of-25 requirement. The computed fields for tenure, promotions and truncation are returned on every profile. They are not filterable in the request in v1. ## Rate limits and concurrency 60 run creations/minute (burst 120), 600 run reads/cancellations/minute (burst 1200), and 120 catalog reads/minute (burst 240), shared across an account's API keys. Token buckets refill continuously. Default concurrency: 20 executing runs and 1000 queued runs per account; deployments may configure different concurrency and queue limits. HTTP 429 includes a Retry-After header in seconds. ## How many profiles to ask for pineapi accepts max_items between 25 and 2,000, in multiples of 25, defaulting to 100. That is not an arbitrary rule: LinkedIn paginates search results in blocks of 25, and a partial block costs exactly what a full one costs. A month of 50,000 profiles is at least 25 searches, since one search caps at 2,000. There is no cheap mode and no deep mode to choose between: there is one tier, and it returns everything. ## Why the official LinkedIn API will not give you profile data LinkedIn's official API does not return other people's profiles. The Sign In with LinkedIn and Share on LinkedIn products return the authenticated member's own name, headline and picture, and the Marketing and Talent APIs sit behind partner programmes with commercial review, headcount requirements and an approved use case. That mismatch is why most searches for "LinkedIn API" are looking for an unofficial one: the official documentation answers a question nobody asked. pineapi exists for the other question: describe who you are looking for, and get those people as structured data. pineapi reads what LinkedIn shows publicly. It is not an official LinkedIn product, it is not affiliated with LinkedIn, and customers must establish a lawful basis for their collection and use of personal data. ## The response schema, field by field pineapi returns 53 top-level fields on every profile, plus 16 nested collections. Every field is listed below with its type and what it contains. **Profile fields**: Generated from the API OpenAPI schema. Nullable values can be absent from the source profile. - `id` (string (uuid)): Id - `ordinal` (integer): Ordinal - `public_identifier` (string | null): Public Identifier - `linkedin_url` (string): Linkedin Url - `requested_url` (string | null): Requested Url - `first_name` (string | null): First Name - `last_name` (string | null): Last Name - `full_name` (string | null): Full Name - `headline` (string | null): Headline - `about` (string | null): About - `location_text` (string | null): Location Text - `location_city` (string | null): Location City - `location_state` (string | null): Location State - `location_region_code` (string | null): Location Region Code - `location_country` (string | null): Location Country - `location_country_full` (string | null): Location Country Full - `location_country_code` (string | null): Location Country Code - `open_to_work` (boolean | null): Open To Work - `hiring` (boolean | null): Hiring - `premium` (boolean | null): Premium - `influencer` (boolean | null): Influencer - `creator` (boolean | null): Creator - `verified` (boolean | null): Verified - `open_profile` (boolean | null): Open Profile - `memorialized` (boolean | null): Memorialized - `connections_count` (integer | null): Connections Count - `follower_count` (integer | null): Follower Count - `registered_at` (string (date-time) | null): Registered At - `profile_language` (string | null): Profile Language - `profile_country` (string | null): Profile Country - `profile_picture_url` (string | null): Profile Picture Url - `photo_url` (string | null): Photo Url - `cover_picture_url` (string | null): Cover Picture Url - `current_title` (string | null): Current Title - `current_company_name` (string | null): Current Company Name - `current_company_linkedin_url` (string | null): Current Company Linkedin Url - `current_position_started_on` (string (date) | null): Current Position Started On - `total_months_experience` (integer | null): Total Months Experience - `months_at_current_company` (integer | null): Months At Current Company - `months_in_current_role` (integer | null): Months In Current Role - `promoted_at_current_company` (boolean): Promoted At Current Company - `promotions_count` (integer): Promotions Count - `companies_count` (integer): Companies Count - `avg_months_per_company` (integer | null): Avg Months Per Company - `linkedin_member_since` (string (date) | null): Linkedin Member Since - `skills_flat` (string[]): Skills Flat - `languages_flat` (string[]): Languages Flat - `school_names` (string[]): School Names - `company_names` (string[]): Company Names - `section_totals` (object | null): Section Totals - `experience_truncated` (boolean): Experience Truncated - `skills_truncated` (boolean): Skills Truncated - `scraped_at` (string (date-time)): Scraped At ### 16 nested collections - `education[]`: Education Sub-fields: . - `certifications[]`: Certifications Sub-fields: . - `languages[]`: Languages Sub-fields: . - `skills[]`: Skills Sub-fields: . - `projects[]`: Projects Sub-fields: . - `publications[]`: Publications Sub-fields: . - `courses[]`: Courses Sub-fields: . - `honors[]`: Honors Sub-fields: . - `volunteering[]`: Volunteering Sub-fields: . - `recommendations[]`: Recommendations Sub-fields: . - `interests[]`: Interests Sub-fields: . - `patents[]`: Patents Sub-fields: . - `organizations[]`: Organizations Sub-fields: . - `websites[]`: Websites Sub-fields: . - `top_skills[]`: Top Skills Sub-fields: . - `experiences[]`: Experiences Sub-fields: ordinal, position, company_name, company_linkedin_url, employment_type, workplace_type, location, description, started_on, ended_on, is_current, duration_months, duration_text, skills. Field names are a contract. pineapi versions the schema under /v1/ and does not rename or remove a field inside a version, because pipelines and agents cache the shape of a response and a silent rename breaks them quietly. Profile responses do not include email addresses. ## When a profile is incomplete, the profile says so 40% of LinkedIn profiles come back with truncated work history and 68% with truncated skills. LinkedIn reports a section's true item count separately from the items it actually serves, and the two frequently disagree. The consequence is arithmetic. Any years-of-experience figure computed from a scraped profile is systematically under-counted on four profiles in ten, so downstream calculations need to check the truncation flags. pineapi returns experience_truncated and skills_truncated as booleans, and section_totals with the count LinkedIn claimed, so you can tell a genuinely short career from a profile that arrived clipped. ## Filters that LinkedIn silently answers with nothing LinkedIn's search stops returning results past a boolean complexity limit. Combine groups with AND or NOT and go beyond 9 OR alternatives and the search returns an empty set with no error, no warning and no indication that the query was the problem. pineapi validates the query before running it and rejects it with HTTP 422, invalid_parameter and an explanation, rather than running a search that was never going to match anything and billing you the $0.12 minimum for it. ## Asynchronous runs and terminal states Searches take minutes and lookups usually take seconds. These observations are not an SLA. Poll until succeeded, partial, failed or cancelled; HTTP 200 on the status endpoint does not mean the run succeeded. Latency is driven by result size more than anything else, so plan around the size you request rather than an average. ## Pagination, and why one big search beats five small ones start_page is a pointer into a LinkedIn search results page, not a stable cursor. LinkedIn re-ranks and re-shuffles results over time, so the same search run twice does not return the same profiles in the same order. pineapi measured about 30% overlap between consecutive paginated calls on the same search. Since billing counts profiles delivered and duplicates are billed, paging through a large result set costs more and returns less of it. Request a large result in a single search with a high max_items instead. One search for 500 profiles is cheaper and more complete than five searches for 100. ## How long results stay available pineapi keeps search results available through the API and as CSV for 30 days. Re-downloading inside that window is free and is not billed again. After 30 days the results are deleted. Store anything you need to keep. ## Errors, retries and what is billed pineapi bills $0.012 per profile delivered, with a $0.12 minimum on any search that runs. Every error carries a machine-readable code, a sentence saying what went wrong and the fix, so an agent can correct itself without a human reading the log. | Code | HTTP | What happened | What to do | Billed | | --- | --- | --- | --- | --- | | `idempotency_key_required` | 400 | The creation request has no Idempotency-Key header. | Send a unique key (a UUID works); reuse the same key and body when retrying. | No new charge | | `invalid_api_key` | 401 | The API key is missing, invalid or revoked. | Send a valid bearer token. | No new charge | | `insufficient_scope` | 403 | The key cannot perform this operation. | Use runs:write for creation/cancellation and runs:read for reads. | No new charge | | `invalid_parameter` | 422 | A field or filter is invalid, including max_items or an overly complex search_query. | Read error.param and error.message; correct the request before retrying. | No new charge | | `insufficient_balance` | 402 | Available credit does not cover the run reservation. | Top up the account before creating the run. | No new charge | | `run_not_found` | 404 | The run does not exist, belongs to another account, or has been deleted. | Check the run_id and retention window. | No new charge | | `idempotency_key_reused` | 409 | The same key was reused for a different request. | Use a new key for new work; keep the original key for an unchanged retry. | No new charge | | `run_not_cancellable` | 409 | The run has already finished. | Only cancel a run that has not finished. | No new charge | | `rate_limited` | 429 | The account token bucket is exhausted. | Wait for the Retry-After header duration before retrying. | No new charge | | `too_many_queued_runs` | 429 | The account queue is full. | Wait for queued work to start; respect Retry-After. | No new charge | | `request_too_large` | 413 | The request body exceeds 1,000,000 bytes. | Reduce the request body. | No new charge | | `service_unavailable` | 503 | The service could not complete this HTTP request in time. | Retry shortly; preserve Idempotency-Key on creation requests. | No new charge | Transient failures are retried inside the search, so HTTP success when reading a run does not mean the run succeeded. Inspect status and failure: failed and cancelled are terminal and not billed. A partial run returns the profiles that were delivered. Retry a failed run as new work only after inspecting the reason. ## No LinkedIn account and no cookies pineapi never asks for your LinkedIn credentials, your session cookie or a browser extension, and it does not act as your account. Tools that do ask for a cookie put your own profile at risk of a restriction, and they stop working the moment you log out on your phone. Because pineapi needs nothing from your account, a key can be issued programmatically and handed to an agent or a CI job with no human in the loop. Create a key from a signed-in dashboard session; paid runs need prepaid balance. ## Questions ### What is the best LinkedIn scraper API? The right one returns the fields you need at a price you can predict without asking for your LinkedIn account. pineapi returns 53 fields plus 16 nested collections for $0.012 per profile delivered, needs no cookie, has no subscription, so a 500-profile month costs what 500 profiles cost. ### How do I scrape LinkedIn profiles programmatically? Describe who you want with filters, start a search with one POST, then read the result when it is done or receive a webhook with the completed run metadata. With pineapi, create a run with a bearer token and Idempotency-Key, poll its status, then download its profiles, with no headless browser to run, no proxy pool to rent and no login to keep alive. The complete request is on this page. ### Is there a LinkedIn scraper that does not need cookies or an account? Yes. pineapi requires no LinkedIn account, no session cookie and no browser extension, Authentication uses a bearer token issued to your account. You remain responsible for lawful use of the returned personal data. ### What fields can I get from a LinkedIn profile? 53 top-level fields plus 16 nested collections: identity, headline and about, parsed location down to country code, status badges, follower and connection counts, current role, computed tenure and promotion counts, flattened skills and schools, truncation flags, and arrays for experience, education, skills, certifications, languages, projects, publications, patents and more. Email addresses are not returned. ### How long does a search take? Searches take minutes; lookups usually take seconds. Poll every 5 seconds for the first minute, then every 20 seconds. Resume with the same run_id if your client stops waiting. ### Does the official LinkedIn API return other people’s profiles? No. The official API returns the authenticated member’s own basic profile, and the richer Talent and Marketing APIs sit behind partner programmes with commercial review. There is no official endpoint that takes a set of filters and returns the people who match, which is why unofficial APIs exist. ### What am I billed when a search returns nothing? The $0.12 search minimum, because the search ran and completed. An empty result means no profile matched your filters, not that anything failed. A search that actually fails is not billed at all, and neither is a query pineapi rejects as too complex before running it. ### Can I filter on the computed fields like promotions or tenure? Not in v1. total_months_experience, months_in_current_role, promotions_count and the truncation flags are computed on every profile pineapi returns, so you can filter on them yourself after the fact, but they are not accepted as search filters in the request. ### How long do you keep search results? 30 days, through the API and as CSV, with free re-downloads inside that window. After 30 days pineapi deletes them. Anything you need for longer belongs in your own storage. ### Will the response schema change? Not inside a version. pineapi versions the schema under /v1/ and does not rename or remove a field within it. New fields can appear; existing ones keep their names and types, because agents and pipelines cache the shape of a response. --- # Pay-as-you-go LinkedIn scraping pricing > What LinkedIn profile data costs: $0.012 per profile delivered, $0.12 minimum per search, no subscription. Work out your monthly bill on this page. URL: https://pineapi.dev/pricing Last updated: 2026-10-19 pineapi charges $0.012 for every LinkedIn profile delivered, $12 per thousand, with a $0.12 minimum on any search that runs. Ten thousand profiles cost $120. There is no subscription, no seat fee and no monthly minimum, and a month with no searches costs nothing. ## One rate, and what it counts pineapi bills profiles delivered, not profiles requested. There are no volume tiers to negotiate into and no enterprise rate behind a call: the price is the same at 500 profiles a month and at 500,000. ## Cost per profile, per credit and per API call pineapi uses a USD prepaid balance, not an abstract credit pack. One delivered profile costs $0.012. A $5 top-up adds $5 of balance; it does not imply that every API call has the same cost. For a completed search, the charge is the greater of $0.12 and delivered profiles × $0.012. For URL lookup, it is delivered profiles × $0.012 with no minimum. The block-of-25 rule applies to the requested search size, not a promise that you will receive or pay for a full block of returned profiles. | Outcome | Charge | Why | | --- | --- | --- | | Search completes with 0 delivered profiles | $0.12 | The search minimum applies | | Search completes with 8 delivered profiles | $0.12 | The calculated profile charge is below the minimum | | Search completes with 25 delivered profiles | $0.30 | 25 × $0.012 | | Lookup returns 80 profiles from 100 unique URLs | $0.96 | 80 × $0.012; 20 unresolved URLs are free | | Poll a run or download existing results again | $0 | No new extraction; results remain subject to retention | | Rule | Value | | --- | --- | | Price per profile delivered | $0.012 ($12 per 1,000) | | Minimum per search | $0.12 | | Minimum per lookup | None; unresolved URLs are free | | Minimum top-up | $5; credit does not expire | | Billing unit | Profiles delivered, not profiles requested. | | Duplicates | Duplicates within a run are discarded and not billed. A profile delivered again in a separate run is billed again. | | Failed searches | A search that fails is not billed. | | Empty searches | A search that completes and matches nobody is billed the $0.12 minimum, because the search ran. | | Subscription, seats, monthly minimum | None | ## What pineapi does not charge for pineapi bills delivered profiles and nothing else. Everything in this list is free, on every account, at every volume. - Seats. Add as many people to an account as you like; the bill does not change. - Monthly fees. A month with no searches costs nothing. - Platform fees. There is no marketplace taking a cut on top of the unit price. - Failed searches. - Setup. What is not on that list is the per-search minimum. Every search that runs bills at least $0.12, including one that matches nobody, and quoting the price without it would be quoting a price that does not exist. ## Monthly cost at the usual volumes | Profiles per month | pineapi monthly cost | | --- | --- | | 1,000 | $12 | | 10,000 | $120 | | 50,000 | $600 | ## How that compares | Tool | Workflow | Published price | Commitment | Check before choosing | | --- | --- | --- | --- | --- | | pineapi | Profile search by filters and URL lookup | $0.012 per delivered profile; $0.12 search minimum; no lookup floor | Prepaid from $5; no subscription | Asynchronous runs; no email enrichment, sandbox or MCP server | | Bright Data | LinkedIn scrapers covering profiles, companies, jobs and posts | $1.50 per 1,000 records on the advertised pay-as-you-go plan | Also advertises a 5,000-record monthly free tier | Compare the selected scraper and its record schema with your required fields | | PhantomBuster | Automation workflows, including LinkedIn outreach and enrichment | Start: $56/month equivalent, $672 billed annually | Subscription bundles execution time, slots and credits | Not a fixed delivered-profile rate; estimate execution and enrichment usage | USD rates checked 2026-10-19. Records are not assumed equivalent. No independent performance benchmark; published by pineapi. [Bright Data: LinkedIn Scraper and pricing](https://brightdata.com/products/web-scraper/linkedin) [PhantomBuster: pricing](https://phantombuster.com/pricing) ## Questions ### How much does one API call cost? Creation starts a paid asynchronous run, not a fixed-price call. A completed search costs at least $0.12, then $0.012 per delivered profile when that total is higher. Lookup has no floor. Polling run status and downloading existing results do not add profile charges. ### Does pineapi use credits? The account holds prepaid USD balance. Top-ups start at $5 and credit does not expire. There is no separate credit-to-profile exchange rate: each delivered profile costs $0.012, subject to the search minimum. ### Is unused account balance refundable? Unused prepaid balance is not refunded when you close your account. Incorrect charges and refunds required by applicable law are exceptions. Contact contact@heytalent.app about a disputed charge. ### How much does it cost to scrape 10,000 LinkedIn profiles? $120 with pineapi, at $0.012 per profile delivered. That is at least 5 searches, since one search returns up to 2,000 profiles, and each of those bills at least the $0.12 minimum. No subscription sits on top and no minimum sits underneath. ### Is there a monthly minimum or a subscription? No. pineapi is pay as you go: you add balance and it is drawn down per profile delivered. A month with no searches costs nothing, there is no plan to downgrade and nothing expires at the end of the month. The only floor is the $0.12 per search that actually runs. ### Do I pay for a search that returns nothing? You pay the $0.12 minimum, because the search ran and no profile matched your filters. A search that genuinely fails is not billed at all. ### Am I billed twice for the same profile? Yes, if it is delivered twice. Billing counts profiles delivered, so a profile appearing in two searches is billed in both. Consecutive paginated calls on the same search overlap by about 30%, which is why one large search costs less than several small ones covering the same ground. ### Does lookup have the same minimum as search? No. Both modes cost $0.012 per delivered profile, but only search has the $0.12 floor. Lookup has no minimum and unresolved URLs are free. ### Are there volume discounts? No, and there is no enterprise rate behind a sales call either. pineapi charges $0.012 per profile delivered at every volume, which means the price you can work out from this page is the price you get. ### Can I set a spending cap? Yes, by holding balance rather than a card on file. pineapi draws down what is on the account and returns insufficient_balance when it runs out, so an agent or a runaway job cannot spend more than you have put in. ### Do re-downloads cost anything? No. Results stay available through the API and as CSV for 30 days and re-downloading inside that window is free. After 30 days they are deleted, so anything you need for longer belongs in your own storage. --- # Best LinkedIn scraper APIs: choose by workflow Reviewed 2026-10-19. Published by pineapi; not an independent performance benchmark. | Tool | Workflow | Published price | Commitment | Check before choosing | | --- | --- | --- | --- | --- | | pineapi | Profile search by filters and URL lookup | $0.012 per delivered profile; $0.12 search minimum; no lookup floor | Prepaid from $5; no subscription | Asynchronous runs; no email enrichment, sandbox or MCP server | | Bright Data | LinkedIn scrapers covering profiles, companies, jobs and posts | $1.50 per 1,000 records on the advertised pay-as-you-go plan | Also advertises a 5,000-record monthly free tier | Compare the selected scraper and its record schema with your required fields | | PhantomBuster | Automation workflows, including LinkedIn outreach and enrichment | Start: $56/month equivalent, $672 billed annually | Subscription bundles execution time, slots and credits | Not a fixed delivered-profile rate; estimate execution and enrichment usage | Prices are USD, checked on the sources below. Records and profiles are not assumed equivalent; free-tier eligibility, taxes, discounts and throughput are not modeled. ## Choose by workflow before comparing the unit price For an application that needs professional-profile JSON, compare search filters, URL lookup and the fields your application consumes. For outreach sequences, compare automation and enrichment instead. The table is a shortlist of different product models, not a performance ranking. pineapi publishes this guide and is one of the products compared; we have not run an independent benchmark of these services. ## When pineapi fits pineapi fits a prepaid integration that searches for people or looks up LinkedIn profile URLs, then reads structured records. It exposes requested_url for matching lookup results, unresolved URLs with reasons, derived tenure and promotion fields, and per-profile truncation flags. Use the generated API reference to check the exact schema. It does not provide contact-email discovery, company-profile endpoints, a no-code scraping dashboard or a native MCP server. ## When to compare another tool Bright Data advertises a lower headline per-record price and a broader LinkedIn scraper catalog. PhantomBuster packages automation workflows with subscription resources. Those may be a better fit for a different workload. A cheaper record is useful only if it includes the fields and retrieval method you need; a larger subscription can make sense when you use its automation features. ## Calculate the cost of your actual workload For pineapi, 1,000 delivered profiles cost $12 before any search minimums that increase the total. A completed search that returns zero profiles costs $0.12. URL lookup has no minimum, so unresolved URLs cost nothing. Duplicate profiles across separate runs are billed again. Repeated status polling and downloading existing results do not create new profile charges. For subscription tools, include the billing commitment and resource limits rather than dividing the subscription by an assumed throughput. ## Check the integration before switching Inspect a sample response, nullable fields, result retention and pagination. Make sure retries preserve the creation request and its Idempotency-Key. Test how your application handles partial runs and missing URLs. Compare credential requirements and your own data-use obligations separately: a product that does not ask for your LinkedIn cookie is not promising that every use of the resulting data is permitted. ## Sources - [Bright Data: LinkedIn Scraper and pricing](https://brightdata.com/products/web-scraper/linkedin) - [PhantomBuster: pricing](https://phantombuster.com/pricing) - [Proxycurl founder: current API status](https://nubela.co/blog/what-is-proxycurl-api-now-in-2026-im-the-founder/) --- # Proxycurl alternative for LinkedIn profile APIs Reviewed 2026-10-19. Proxycurl’s founder confirms the original service is shut down and presents NinjaPear as a different successor product. Source: [founder’s status update](https://nubela.co/blog/what-is-proxycurl-api-now-in-2026-im-the-founder/). ## A replacement for the profile workflow pineapi is an option for replacing a profile-URL or filter-search integration. It is not a drop-in replacement for the entire Proxycurl product. Endpoint paths, request shapes, authentication credentials and response handling must be adapted. If your application depends on company enrichment, contact details or synchronous responses, identify those dependencies before changing providers. ## Build an adapter around runs Keep your application’s existing input and output interfaces, then implement a pineapi adapter behind them. Persist the request body, Idempotency-Key and returned run_id. Poll every 5 seconds for the first minute and every 20 seconds afterwards until succeeded, partial, failed or cancelled. Read /v1/runs/{run_id}/profiles and pass next_cursor back as cursor until has_more is false. Keep run_id when your client times out so it can resume without starting a second paid run. ## Map fields from your stored responses Use your own saved response fixtures as the old contract and the generated pineapi OpenAPI document as the new one. Match identity fields deliberately; inspect experiences, current_title, current_company_name and the tenure fields rather than relying on similarly named fields having identical meaning. Preserve null values. Check experience_truncated and skills_truncated before treating a missing source section as an empty career or skill set. ## Reconcile every requested URL Use requested_url to connect each returned record to your normalized input list. Inspect unresolved separately and report its reason to the caller. A renamed LinkedIn slug can change linkedin_url, so it is not a safe join key for your original request. A duplicate inside one run is discarded; the same profile delivered again in a different run is billed again. ## Budget the migration sample Use a small, deliberately selected batch for the first paid run. A lookup delivering 100 profiles costs $1.20; unresolved URLs add no charge. Search costs at least $0.12 per completed run. Add prepaid balance from $5; there is no sandbox, free trial or transferable legacy credit balance. Compare your adapter’s output before replacing production traffic. ## Migration map | Existing workflow | pineapi approach | Change required | | --- | --- | --- | | A list of LinkedIn profile URLs | POST /v1/profile-lookups with linkedin_urls | 1–2,000 URLs; normalize and deduplicate before reconciliation | | People discovery using filters | POST /v1/profile-searches | Rebuild the request from the current catalogs; do not copy old filter IDs | | One call returning a profile object | Create → poll run → read profiles | HTTP 202 contains run_id, not the finished profile | | Matching returned rows to your input | requested_url on each returned profile | Do not join on linkedin_url: the current slug may differ | | Missing or renamed profiles | unresolved on the run | not_found or unavailable; these URLs are not billed | | Company, email or phone enrichment | No equivalent pineapi endpoint | Keep this outside the migration scope | Next: [Quickstart](https://pineapi.dev/quickstart) · [API reference](https://pineapi.dev/api-reference) · [Pricing](https://pineapi.dev/pricing).