> ## 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 Skill Endorsers

> Fetch every member who endorsed one skill of a professional profile

## Endpoint

```
GET /api/v1/profile/skills/endorsers
```

## Authentication

Include your API key in the request header:

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

## How it works

Each skill on [Get Profile](https://docs.fetchin.io/api-reference/endpoint/get-profile.md) and [Get Profile Skills](https://docs.fetchin.io/api-reference/endpoint/get-profile-skills.md) carries a `skillUrn`:

```json theme={null}
{
  "name": "Public Speaking",
  "skillUrn": "urn:li:fsd_skill:(ACoAAAAteRwB-gns09UDa-RYj02ngoedsj7C9QM,25)"
}
```

This endpoint returns every member who endorsed that skill, not only the few a profile displays: one page of up to 100 per call. Each endorser comes with their public identifier and profile URL, so you can trace them back to a profile with [Get Profile](https://docs.fetchin.io/api-reference/endpoint/get-profile.md).

## Parameters

**profileUrlOrUrn** `string` *required*

The professional profile the skill belongs to. Accepts any of:

* **Profile URN**. Example: `urn:li:fsd_profile:ACoAAAAteRwB-gns09UDa-RYj02ngoedsj7C9QM`
* **Public identifier (slug)**. Example: `billcorbettauthor`
* **Profile URL**. Example: `https://www.linkedin.com/in/billcorbettauthor` (a trailing slash makes no difference)

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

**skillUrn** `string` *required*

The skill, exactly as [Get Profile](https://docs.fetchin.io/api-reference/endpoint/get-profile.md) returns it in `skills[].skillUrn`. Example: `urn:li:fsd_skill:(ACoAAAAteRwB-gns09UDa-RYj02ngoedsj7C9QM,25)`

It must belong to `profileUrlOrUrn`. A value that is not a skill URN (a skill name, for instance), or a skill of another member when `profileUrlOrUrn` is a URN, is rejected with a free `400` `INVALID_PARAMETER`, before anything is fetched.

**count** `integer` (default: 100)

Number of endorsers per page.

* Minimum: 1 — `0` or a negative value is rejected with `400`
* Maximum: 100 — a higher value is clamped, not rejected
* Default: 100, the most a page holds. A call costs the same whatever the `count`.

**start** `integer` (default: 0)

Offset of the first endorser to return. Pass `0` (or omit it) for the first page, then the `nextStart` the previous response gave you. See [Pagination](#pagination).

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

## Response

**endorsers** `array`

The members who endorsed the skill, in the order the source lists them. Empty when nobody did, or when `start` is past the end of the list.

**Endorser object**

**publicIdentifier** `string`

The endorser's public identifier (slug). Pass it to [Get Profile](https://docs.fetchin.io/api-reference/endpoint/get-profile.md) to fetch their profile. It is the only identifier the source gives for an endorser.

**url** `string`

The endorser's professional profile URL.

**name** `string`

The endorser's name, as they display it.

**headline** `string`

The endorser's headline, as they wrote it. Absent when they have none.

**profilePictureUrl** `string`

The largest rendition of the endorser's photo. Absent when they have none.

**profilePictureUrls** `array`

Every rendition of the photo, smallest first, each `{ url, width, height }`. Absent when they have none. The URLs are signed and expire, so fetch them rather than storing them for months.

**hasMore** `boolean`

`true` when the source has more endorsers after this page. `false` on the last page.

**nextStart** `integer`

The `start` value to request next. Present **only** when `hasMore` is `true`.

## Example Request

```bash cURL theme={null}
curl -G "https://api.fetchin.io/api/v1/profile/skills/endorsers" \
  --data-urlencode "profileUrlOrUrn=billcorbettauthor" \
  --data-urlencode "skillUrn=urn:li:fsd_skill:(ACoAAAAteRwB-gns09UDa-RYj02ngoedsj7C9QM,25)" \
  -H "X-API-Key: your-api-key-here"
```

```javascript JavaScript theme={null}
const params = new URLSearchParams({
  profileUrlOrUrn: 'billcorbettauthor',
  skillUrn: 'urn:li:fsd_skill:(ACoAAAAteRwB-gns09UDa-RYj02ngoedsj7C9QM,25)'
});

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

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

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

response = requests.get(
    'https://api.fetchin.io/api/v1/profile/skills/endorsers',
    params={
        'profileUrlOrUrn': 'billcorbettauthor',
        'skillUrn': 'urn:li:fsd_skill:(ACoAAAAteRwB-gns09UDa-RYj02ngoedsj7C9QM,25)',
    },
    headers={'X-API-Key': 'your-api-key-here'},
)

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

## Example Response

```json theme={null}
{
  "endorsers": [
    {
      "publicIdentifier": "jane-doe",
      "url": "https://www.linkedin.com/in/jane-doe/",
      "name": "Jane Doe",
      "headline": "VP Engineering at Example Corp",
      "profilePictureUrl": "https://media.licdn.com/dms/image/v2/.../profile-displayphoto-shrink_800_800/...",
      "profilePictureUrls": [
        { "url": "https://media.licdn.com/dms/image/v2/.../profile-displayphoto-shrink_100_100/...", "width": 100, "height": 100 },
        { "url": "https://media.licdn.com/dms/image/v2/.../profile-displayphoto-shrink_800_800/...", "width": 800, "height": 800 }
      ]
    },
    {
      "publicIdentifier": "john-smith-4b2a",
      "url": "https://www.linkedin.com/in/john-smith-4b2a/",
      "name": "John Smith",
      "headline": "Founder at Example Labs"
    }
  ],
  "hasMore": true,
  "nextStart": 100
}
```

## Errors

See [Error Handling](https://docs.fetchin.io/concepts/error-handling.md) for the full list of error codes and recommended handling.

* `400` — `MISSING_PARAMETER` / `INVALID_PARAMETER` / `INVALID_URN`
* `401` — `INVALID_API_KEY`
* `402` — `QUOTA_EXHAUSTED`
* `404` — `PROFILE_NOT_FOUND`
* `429` — `RATE_LIMITED`
* `500` — `INTERNAL_ERROR`
* `503` — `SERVICE_UNAVAILABLE`. The page could not be fetched on this call: retry. A failed page is never returned as an empty list.

## Pagination

The list is paged by **offset**, up to 100 endorsers per page.

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`.

```javascript theme={null}
async function fetchAllEndorsers(profile, skillUrn) {
  const endorsers = [];
  let start = 0;
  let hasMore = true;

  while (hasMore) {
    const params = new URLSearchParams({
      profileUrlOrUrn: profile,
      skillUrn,
      start: String(start)
    });

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

    const data = await response.json();
    endorsers.push(...data.endorsers);
    hasMore = data.hasMore;
    start = data.nextStart ?? start;
  }

  return endorsers;
}
```

## Notes

> [!NOTE]
>
> This endpoint consumes 1 credit per call, whatever the `count`. A skill with 250 endorsers takes three calls. Errors are free — with one exception: a `404` not found costs 1 credit. See [Quotas](https://docs.fetchin.io/concepts/quotas.md).

> [!TIP]
>
> [Get Profile Skills](https://docs.fetchin.io/api-reference/endpoint/get-profile-skills.md) tells you how many endorsers each skill has, so you can skip the skills nobody endorsed and know in advance how many pages each one needs.

> [!WARNING]
>
> The source shows the endorsements a member has **received**, skill by skill. It does not show the endorsements a member gave to others, nor when an endorsement was given, so neither is available.


## OpenAPI

````yaml GET /api/v1/profile/skills/endorsers
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/skills/endorsers:
    get:
      summary: Get the members who endorsed a skill
      description: >-
        Fetch every member who endorsed one skill of a profile, up to 100 per
        page. Take `skillUrn` from `skills[].skillUrn` of GET /api/v1/profile or
        GET /api/v1/profile/skills. Paged by offset: keep paging with
        `nextStart` while `hasMore` is true. One credit per call. The source
        shows the endorsements a member received, not the ones they gave, and
        does not date them.
      parameters:
        - name: profileUrlOrUrn
          in: query
          required: true
          description: >-
            The professional profile. Accepts a profile URN (example
            `urn:li:fsd_profile:ACoAAAAteRwB-gns09UDa-RYj02ngoedsj7C9QM`), a
            public identifier/slug (example `billcorbettauthor`), or a full
            profile URL (example
            `https://www.linkedin.com/in/billcorbettauthor`; a trailing slash is
            ignored). Member profiles only: a company page URL or URN is
            rejected with a `400` error, free of charge.
          schema:
            type: string
            example: billcorbettauthor
        - name: skillUrn
          in: query
          required: true
          description: >-
            The skill, exactly as GET /api/v1/profile returns it in
            `skills[].skillUrn`. It must belong to `profileUrlOrUrn`. A value
            that is not a skill URN (a skill name, for instance), or a skill of
            another member when `profileUrlOrUrn` is a URN, returns 400
            INVALID_PARAMETER at no cost, before anything is fetched.
          schema:
            type: string
            example: urn:li:fsd_skill:(ACoAAAAteRwB-gns09UDa-RYj02ngoedsj7C9QM,25)
        - name: count
          in: query
          required: false
          description: >-
            Number of entries per page (default: 100, the most a page holds). A
            value above 100 is clamped to 100, not rejected. A call costs the
            same whatever the count.
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
        - name: start
          in: query
          required: false
          description: >-
            Offset of the first entry to return. Pass 0 (or omit) for the first
            page, then the `nextStart` of the previous response. Keep paging
            while `hasMore` is true.
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Successfully fetched the endorsers
          content:
            application/json:
              schema:
                type: object
                properties:
                  endorsers:
                    type: array
                    description: >-
                      The members who endorsed the skill, in the order the
                      source lists them. Empty when nobody did, or when `start`
                      is past the end of the list.
                    items:
                      type: object
                      properties:
                        publicIdentifier:
                          type: string
                          example: jane-doe
                          description: >-
                            The endorser's public identifier (slug): pass it to
                            GET /api/v1/profile to fetch their profile. It is
                            the only identifier the source gives for an
                            endorser.
                        url:
                          type: string
                          example: https://www.linkedin.com/in/jane-doe/
                          description: The endorser's professional profile URL.
                        name:
                          type: string
                          example: Jane Doe
                          description: The endorser's name, as they display it.
                        headline:
                          type: string
                          example: VP Engineering at Example Corp
                          description: >-
                            The endorser's headline, as they wrote it. Absent
                            when they have none.
                        profilePictureUrl:
                          type: string
                          example: https://media.licdn.com/dms/image/...
                          description: >-
                            The largest rendition of the endorser's photo.
                            Absent when they have none.
                        profilePictureUrls:
                          type: array
                          description: >-
                            Every rendition of the photo, smallest first. Absent
                            when they have none. The URLs are signed and expire.
                          items:
                            type: object
                            properties:
                              url:
                                type: string
                              width:
                                type: integer
                              height:
                                type: integer
                  hasMore:
                    type: boolean
                    description: >-
                      True when the source has more entries after this page,
                      false on the last page.
                  nextStart:
                    type: integer
                    example: 100
                    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.