> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fetchin.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Profile Articles

> Fetch the long-form articles a professional profile has published

## Endpoint

```
GET /api/v1/profile/articles
```

## Authentication

Include your API key in the request header:

```bash theme={null}
X-API-Key: your-api-key-here
```

## Parameters

<ParamField query="profileUrlOrUrn" type="string" required>
  The professional profile whose published articles to fetch. Accepts any of:

  * **Profile URN**, recommended for consistency: a profile's public identifier (slug) can change over time, the URN does not. Example: `urn:li:fsd_profile:ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc`
  * **Public identifier (slug)**, also fine. Example: `williamhgates`
  * **Profile URL**. Example: `https://www.linkedin.com/in/williamhgates` (a trailing slash makes no difference)

  The slug is the last path segment of a profile URL. Pass just the slug, not the `/in/` prefix.

  Member profiles only: a company page URL or URN is rejected with a `400` error, free of charge.
</ParamField>

<ParamField query="count" type="integer" default="10">
  Number of articles to fetch per page.

  * Minimum: 1 — `0` or a negative value is rejected with `400`
  * Maximum: 100 — a higher value is clamped, not rejected
  * Default: 10
</ParamField>

<ParamField query="start" type="integer" default="0">
  Offset of the first article to return, counted from the newest. **Supported on
  this endpoint** — unlike the other profile feeds, this one is paged by offset
  rather than by cursor. Pass `0` (or omit it) for the first page, then advance
  it by the `count` you requested. The response hands you the next value as
  `nextStart`. See [Pagination](#pagination).

  * Minimum: 0 — a negative value is rejected with `400`
  * Maximum: 100,000 — a higher value is capped, not rejected
</ParamField>

## Response

<ResponseField name="articles" type="array">
  Array of published articles, newest first

  <Expandable title="Article object">
    <ResponseField name="articleUrn" type="string">
      The article's identifier. Example: `urn:li:linkedInArticle:7280956270201704448`
    </ResponseField>

    <ResponseField name="articleId" type="string">
      The bare numeric id, without the URN prefix. Example: `7280956270201704448`
    </ResponseField>

    <ResponseField name="title" type="string">
      Title of the article.
    </ResponseField>

    <ResponseField name="url" type="string">
      Public permalink to the article. Tracking parameters are already stripped,
      so the value is stable across calls and safe to use as a key.
    </ResponseField>

    <ResponseField name="description" type="string">
      The opening of the article body as the feed returns it — roughly 150
      characters. Not the full text; see [Notes](#notes).
    </ResponseField>

    <ResponseField name="publishedAt" type="string | null">
      ISO 8601 timestamp of publication, derived from the article id. `null` when
      the id is not decodable — never a guessed date.
    </ResponseField>

    <ResponseField name="coverImageUrl" type="string | null">
      The largest available cover image. `null` when the article has none.
    </ResponseField>

    <ResponseField name="readTimeMinutes" type="integer | null">
      Reading time in minutes, as estimated upstream. `null` when it cannot be
      read.
    </ResponseField>

    <ResponseField name="subtitle" type="string">
      The byline verbatim, as shown on the article card. Example:
      `by Ada Lovelace • 9 min read`
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="hasMore" type="boolean">
  `true` while this page came back with articles, which means it is worth asking
  for the next offset. The last populated page also reports `true` — the feed
  signals its end with an empty page, so expect one final request that returns
  `articles: []` and `hasMore: false`. See [Pagination](#pagination).
</ResponseField>

<ResponseField name="nextStart" type="integer">
  The `start` value to request next. Present **only** when `hasMore` is `true`.
</ResponseField>

## Example Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET "https://api.fetchin.io/api/v1/profile/articles?profileUrlOrUrn=https://www.linkedin.com/in/williamhgates&count=5" \
    -H "X-API-Key: your-api-key-here"
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    'https://api.fetchin.io/api/v1/profile/articles?profileUrlOrUrn=https://www.linkedin.com/in/williamhgates&count=5',
    {
      headers: {
        'X-API-Key': 'your-api-key-here'
      }
    }
  );

  const data = await response.json();
  console.log(data.articles);
  ```

  ```python Python theme={null}
  import requests

  headers = {
      'X-API-Key': 'your-api-key-here'
  }

  params = {
      'profileUrlOrUrn': 'https://www.linkedin.com/in/williamhgates',
      'count': 5
  }

  response = requests.get(
      'https://api.fetchin.io/api/v1/profile/articles',
      headers=headers,
      params=params
  )

  data = response.json()
  print(data['articles'])
  ```

  ```php PHP theme={null}
  <?php

  $apiKey = 'your-api-key-here';
  $profileUrl = 'https://www.linkedin.com/in/williamhgates';
  $count = 5;

  $url = "https://api.fetchin.io/api/v1/profile/articles?profileUrlOrUrn=" . urlencode($profileUrl) . "&count=" . $count;

  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . $apiKey
  ]);

  $response = curl_exec($ch);
  $data = json_decode($response, true);

  curl_close($ch);
  print_r($data['articles']);
  ?>
  ```
</CodeGroup>

## Example Response

<ResponseExample>
  ```json 200 Success theme={null}
  {
    "articles": [
      {
        "articleUrn": "urn:li:linkedInArticle:7280956270201704448",
        "articleId": "7280956270201704448",
        "title": "What I learned shipping software for thirty years",
        "url": "https://www.linkedin.com/pulse/what-i-learned-shipping-software-thirty-years-williamhgates",
        "description": "Every release teaches you something the last one could not. Here are the lessons that kept coming back, and the ones I had to...",
        "publishedAt": "2026-07-14T08:12:03.000Z",
        "coverImageUrl": "https://media.licdn.com/dms/image/...",
        "readTimeMinutes": 9,
        "subtitle": "by Ada Lovelace • 9 min read"
      },
      {
        "articleUrn": "urn:li:linkedInArticle:7264411902883401728",
        "articleId": "7264411902883401728",
        "title": "The energy transition is an engineering problem",
        "url": "https://www.linkedin.com/pulse/energy-transition-engineering-problem-williamhgates",
        "description": "We talk about climate in terms of pledges, but the hard part is industrial: steel, cement, fertiliser. None of them have a...",
        "publishedAt": "2026-05-02T16:40:55.000Z",
        "coverImageUrl": null,
        "readTimeMinutes": 6,
        "subtitle": "by Ada Lovelace • 6 min read"
      },
      {
        "articleUrn": "urn:li:linkedInArticle:7231887450119684096",
        "articleId": "7231887450119684096",
        "title": "Notes from a year of reading",
        "url": "https://www.linkedin.com/pulse/notes-from-year-reading-williamhgates",
        "description": "Five books that changed how I think about measurement, and one that I gave up on halfway through without any regret at...",
        "publishedAt": "2026-01-19T11:05:21.000Z",
        "coverImageUrl": "https://media.licdn.com/dms/image/...",
        "readTimeMinutes": null,
        "subtitle": "by Ada Lovelace"
      }
    ],
    "hasMore": true,
    "nextStart": 10
  }
  ```

  ```json 400 Bad Request theme={null}
  {
    "error": "Missing profileUrlOrUrn parameter",
    "code": "MISSING_PARAMETER"
  }
  ```

  ```json 401 Unauthorized theme={null}
  {
    "error": "Invalid API key",
    "code": "INVALID_API_KEY"
  }
  ```

  ```json 404 Not Found theme={null}
  {
    "error": "<human-readable reason>",
    "code": "PROFILE_NOT_FOUND"
  }
  ```

  Branch on `code`, never on `error`: the `code` is a frozen contract, while the
  `error` message is informational and may be reworded.

  ```json 402 Payment Required theme={null}
  {
    "error": "Quota exceeded. Upgrade your plan or wait for renewal.",
    "code": "QUOTA_EXHAUSTED",
    "details": {
      "creditsRemaining": 0,
      "subscriptionCreditsRemaining": 0,
      "paygCreditsRemaining": 0,
      "buyCreditsUrl": "https://fetchin.io/credits"
    }
  }
  ```

  ```json 500 Internal Server Error theme={null}
  {
    "error": "Internal server error",
    "code": "INTERNAL_ERROR"
  }
  ```

  ```json 503 Service Unavailable theme={null}
  {
    "error": "Service temporarily unable to serve this request. Please retry.",
    "code": "SERVICE_UNAVAILABLE"
  }
  ```
</ResponseExample>

## Errors

See [Error Handling](/concepts/error-handling) for the full list of error codes and recommended handling.

* `400` — `MISSING_PARAMETER` / `INVALID_PARAMETER` / `INVALID_URN`
* `401` — `INVALID_API_KEY`
* `404` — `PROFILE_NOT_FOUND`
* `402` — `QUOTA_EXHAUSTED`
* `429` — `RATE_LIMITED`
* `500` — `INTERNAL_ERROR`
* `503` — `SERVICE_UNAVAILABLE`

## Pagination

This is the one profile feed paged by **offset** rather than by cursor, because
upstream exposes no cursor for it. There is no `paginationToken` on this
endpoint.

1. Request the first page with `start=0` (or omit `start`).
2. If `hasMore` is `true`, request the next page with the `nextStart` the
   response gave you.
3. Continue until `hasMore` is `false`.

`nextStart` advances by the `count` you **requested**, not by the number of
articles you received. If you build the offset yourself, add your requested
`count` — prefer `nextStart`, which already holds that value.

`count` is a ceiling, not a quota: a page can come back with fewer articles than
you asked for (a requested 20 routinely delivers 19) because the window indexes
the underlying list and items it cannot represent are dropped from the page. A
short page is **not** the end of the feed — only an empty page is.

```javascript theme={null}
async function fetchAllArticles(profileUrl) {
  const allArticles = [];
  let start = 0;
  let hasMore = true;

  while (hasMore) {
    const params = new URLSearchParams({
      profileUrlOrUrn: profileUrl,
      count: '20',
      start: String(start)
    });

    const response = await fetch(
      `https://api.fetchin.io/api/v1/profile/articles?${params}`,
      { headers: { 'X-API-Key': 'your-api-key-here' } }
    );

    const data = await response.json();
    allArticles.push(...data.articles);

    // Advance by nextStart, never by data.articles.length:
    // a short page is normal and does not mean the feed is over.
    start = data.nextStart ?? start + 20;
    hasMore = data.hasMore;
  }

  return allArticles;
}
```

## Notes

<Info>
  This endpoint consumes 1 credit from your credit balance per API call, regardless of the `count` parameter value. Errors are free — with one exception: a `404` not found costs 1 credit. See [Quotas](/concepts/quotas).
</Info>

<Warning>
  Stop paging on `hasMore: false`, never on a short page. A page holding fewer
  articles than the `count` you requested is normal, and a client that treats
  the first short page as the end of the feed will silently miss articles.
</Warning>

<Warning>
  This feed carries metadata only. There are no engagement counts — no reaction
  or comment totals — and no article body: `description` is the opening of the
  text, around 150 characters. Do not expect those fields to appear on an
  article object.
</Warning>

<Info>
  A company page URL or URN is rejected with a free `400` `INVALID_PARAMETER`.
  For organizations, use
  [Get Company Posts](/api-reference/endpoint/get-company-posts).
</Info>

<Tip>
  Articles come back newest first, so an incremental sync only needs the first
  page or two: page until you reach an `articleUrn` you already stored, then
  stop.
</Tip>

## Use Cases

<CardGroup cols={2}>
  <Card title="Thought-leadership tracking" icon="feather">
    Follow the long-form pieces a prospect or competitor publishes over time
  </Card>

  <Card title="Expertise mapping" icon="brain">
    Infer what a member writes about in depth, beyond their headline
  </Card>

  <Card title="Content library" icon="books">
    Build a durable index of a profile's published writing, keyed on `articleUrn`
  </Card>

  <Card title="Outreach personalisation" icon="comment-dots">
    Open a conversation on an article the person actually wrote
  </Card>
</CardGroup>


## OpenAPI

````yaml GET /api/v1/profile/articles
openapi: 3.1.0
info:
  title: Fetchin API
  version: 1.0.0
  description: Public B2B data API for fetching posts and profile information
servers:
  - url: https://api.fetchin.io
    description: Production server
security:
  - ApiKeyAuth: []
paths:
  /api/v1/profile/articles:
    get:
      summary: Get articles a profile has published
      description: >-
        Fetch the long-form articles a member has published (their articles
        tab), newest first. Metadata only: no engagement counts and no article
        body. This is the one profile feed paged by offset rather than by
        cursor, so `start` is supported here and there is no `paginationToken`.
        Advance `start` by the `count` you requested (the response returns that
        value as `nextStart`) and keep paging while `hasMore` is true. `count`
        is a ceiling, not a quota: a page can hold fewer articles than requested
        because the window indexes the underlying list and items it cannot
        represent are dropped from the page, so a short page is not the end of
        the feed - only an empty page is.
      parameters:
        - name: profileUrlOrUrn
          in: query
          required: true
          description: >-
            The professional profile to fetch. Accepts a profile URN
            (recommended for consistency, since a profile's public identifier
            can change over time while the URN does not; example
            `urn:li:fsd_profile:ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc`), a
            public identifier/slug (also fine; example `williamhgates`), or a
            full profile URL (example
            `https://www.linkedin.com/in/williamhgates`; a trailing slash is
            ignored). The slug is the last path segment of a profile URL, so
            pass just the slug, not the `/in/` prefix. Member profiles only: a
            company page URL or URN is rejected with a `400` error, free of
            charge.
          schema:
            type: string
            example: urn:li:fsd_profile:ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc
        - name: count
          in: query
          required: false
          description: >-
            Number of articles to fetch per page (default: 10). A value above
            100 is clamped to 100, not rejected. The value is a ceiling: a page
            can come back with fewer articles than requested.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 10
        - name: start
          in: query
          required: false
          description: >-
            Offset of the first article to return, counted from the newest.
            Supported on this endpoint: this feed is paged by offset, not by
            cursor. Pass 0 (or omit) for the first page, then advance by the
            `count` you requested - the response hands you that value as
            `nextStart`. Keep paging while `hasMore` is true.
          schema:
            type: integer
            minimum: 0
            default: 0
            example: 20
      responses:
        '200':
          description: Successfully fetched profile articles
          content:
            application/json:
              schema:
                type: object
                properties:
                  articles:
                    type: array
                    description: Published articles, newest first.
                    items:
                      type: object
                      properties:
                        articleUrn:
                          type: string
                          example: urn:li:linkedInArticle:7280956270201704448
                          description: The article's identifier.
                        articleId:
                          type: string
                          example: '7280956270201704448'
                          description: The bare numeric id, without the URN prefix.
                        title:
                          type: string
                          example: What I learned shipping software for thirty years
                          description: Title of the article.
                        url:
                          type: string
                          example: >-
                            https://www.linkedin.com/pulse/what-i-learned-shipping-software-thirty-years-williamhgates
                          description: >-
                            Public permalink to the article, with tracking
                            parameters already stripped, so the value is stable
                            across calls.
                        description:
                          type: string
                          example: >-
                            Every release teaches you something the last one
                            could not. Here are the lessons that kept coming
                            back, and the ones I had to...
                          description: >-
                            The opening of the article body as the feed returns
                            it, roughly 150 characters. Not the full text.
                        publishedAt:
                          type: string
                          format: date-time
                          nullable: true
                          example: '2026-07-14T08:12:03.000Z'
                          description: >-
                            ISO 8601 publication timestamp, derived from the
                            article id. Null when the id is not decodable -
                            never a guessed date.
                        coverImageUrl:
                          type: string
                          nullable: true
                          example: https://media.licdn.com/dms/image/...
                          description: >-
                            The largest available cover image. Null when the
                            article has none.
                        readTimeMinutes:
                          type: integer
                          nullable: true
                          example: 9
                          description: >-
                            Reading time in minutes, as estimated upstream. Null
                            when it cannot be read.
                        subtitle:
                          type: string
                          example: by Ada Lovelace • 9 min read
                          description: The byline verbatim, as shown on the article card.
                  hasMore:
                    type: boolean
                    description: >-
                      Whether the feed holds more articles behind this page.
                      Stop paging when it is false; a short page is not the end
                      of the feed.
                  nextStart:
                    type: integer
                    example: 10
                    description: >-
                      The `start` value to request next. Present only when
                      `hasMore` is true.
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
components:
  responses:
    BadRequest:
      description: Bad request - a required parameter is missing or a value is invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missingParameter:
              value:
                error: Missing profileUrlOrUrn parameter
                code: MISSING_PARAMETER
            invalidParameter:
              value:
                error: limit must be a number between 1 and 100
                code: INVALID_PARAMETER
            invalidUrn:
              value:
                error: 'Invalid URN format: urn:li:member:123456789'
                code: INVALID_URN
    Unauthorized:
      description: Unauthorized - the X-API-Key header is missing or invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missingApiKey:
              value:
                error: Missing API key. Include X-API-Key header.
                code: UNAUTHENTICATED
            invalidApiKey:
              value:
                error: Invalid API key
                code: INVALID_API_KEY
    PaymentRequired:
      description: >-
        Payment required - you are out of credits (QUOTA_EXHAUSTED): the plan
        allowance for the current period is spent AND no pay-as-you-go credits
        are left. Buy pay-as-you-go credits (self-serve,
        https://fetchin.io/credits), upgrade your plan, or wait for renewal;
        retrying will not help and no Retry-After is sent. Distinct from the
        per-second rate limit, which is a 429 (RATE_LIMITED).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            quotaExhausted:
              value:
                error: Quota exceeded. Upgrade your plan or wait for renewal.
                code: QUOTA_EXHAUSTED
                details:
                  creditsRemaining: 0
                  subscriptionCreditsRemaining: 0
                  paygCreditsRemaining: 0
                  buyCreditsUrl: https://fetchin.io/credits
    NotFound:
      description: >-
        Not found - the request was served successfully but the requested
        professional entity does not exist.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            profileNotFound:
              value:
                error: LinkedIn profile not found
                code: PROFILE_NOT_FOUND
            postNotFound:
              value:
                error: Unable to find post from url
                code: POST_NOT_FOUND
            companyNotFound:
              value:
                error: Unable to find company slug from urn
                code: COMPANY_NOT_FOUND
    TooManyRequests:
      description: >-
        Too many requests - your per-second rate limit (RATE_LIMITED) was
        exceeded. Back off and retry, honoring the Retry-After header. (Running
        out of credits is a separate 402 PaymentRequired / QUOTA_EXHAUSTED, and
        buying credits never raises this per-second limit.)
      headers:
        Retry-After:
          description: Number of seconds to wait before retrying the request.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            rateLimited:
              value:
                error: Rate limit exceeded. Try again later.
                code: RATE_LIMITED
    InternalError:
      description: >-
        Internal server error - an unexpected error occurred in our API. Safe to
        retry; contact support if it persists.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            internalError:
              value:
                error: Internal server error
                code: INTERNAL_ERROR
    ServiceUnavailable:
      description: >-
        Service unavailable - our API is temporarily unable to serve this
        request (capacity exhausted or overloaded). This is on our side, not
        yours and not an upstream service outage. Retry with backoff, honoring
        the Retry-After header.
      headers:
        Retry-After:
          description: Number of seconds to wait before retrying the request.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            serviceUnavailable:
              value:
                error: >-
                  Service temporarily unable to serve this request. Please
                  retry.
                code: SERVICE_UNAVAILABLE
  schemas:
    Error:
      type: object
      required:
        - error
        - code
      properties:
        error:
          type: string
          description: >-
            Human-readable description of what went wrong. For display/logging;
            do not branch on this string.
        code:
          type: string
          enum:
            - MISSING_PARAMETER
            - INVALID_PARAMETER
            - INVALID_URN
            - UNAUTHENTICATED
            - INVALID_API_KEY
            - FORBIDDEN
            - NOT_FOUND
            - PROFILE_NOT_FOUND
            - POST_NOT_FOUND
            - COMPANY_NOT_FOUND
            - ENDPOINT_REMOVED
            - RATE_LIMITED
            - QUOTA_EXHAUSTED
            - INTERNAL_ERROR
            - UPSTREAM_ERROR
            - SERVICE_UNAVAILABLE
            - UPSTREAM_TIMEOUT
          description: >-
            Stable, machine-readable error code. Branch your integration on this
            value, not on the HTTP status alone or the message text.
        details:
          type: object
          additionalProperties: true
          description: >-
            Optional structured context. On QUOTA_EXHAUSTED it carries the
            balance breakdown at the moment of the rejection - creditsRemaining,
            subscriptionCreditsRemaining, paygCreditsRemaining - plus
            buyCreditsUrl, the self-serve page for buying more credits. Always
            additive and never guaranteed: read it for diagnostics and logs, but
            branch on `code` and do not require any field of it.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.