Datacircle

LinkedIn profiles in Semantic Kernel: a LinkedIn plugin through MCP or a C# kernel function

You're building an app in C# with Semantic Kernel: a kernel with a chat model and automatic function calling. The model should read a LinkedIn profile from its URL: the job title, company, location and headline. Microsoft's overview says "Semantic Kernel is a lightweight, open-source development kit that lets you easily build AI agents and integrate the latest AI models into your C#, Python, or Java codebase." Semantic Kernel has no LinkedIn plugin of its own.

Semantic Kernel's README calls Microsoft Agent Framework "the enterprise-ready successor to Semantic Kernel", and Microsoft says it will "continue to support Semantic Kernel v1.x for the foreseeable future". We wrote the same tool for Agent Framework, in Python: LinkedIn profiles in Microsoft Agent Framework.

Datacircle is a data co-op. Step 1: Query your favorite B2B data APIs through us. Same request, same price, no markup. Step 2: You're DONE. Every morning, you get the flat file of your data plus everyone else's. Add $50 to your account: you get $50 of API PLUS the flat file. Right now we have 3 live LinkedIn profile APIs that we trust: Up2Data, HarvestAPI and Fetchin.

You can add our MCP server to your kernel as a plugin, and write no code for the call. Or you can write one kernel function that calls our API, and decide what the model reads. Both call Up2Data first. Up2Data costs $2.375 per 1,000 profiles it finds, and nothing for a profile it can't find. Fetchin is cheaper at $1.485 per 1,000, but it charges for a profile it can't find, and all our customers share its rate limit. The function switches to Fetchin only when Up2Data hits its daily limit and returns a 429, and the MCP app's system message tells the model to do the same.

We ran both apps on .NET 10 and .NET 8, with a scripted stand-in for OpenAI's API as the model, and a stand-in server that answers like our API. We called api.datacircle.dev with a wrong key, from the kernel function and from the MCP app: each got a 401. We haven't run either with a real key or a real model.

Semantic Kernel has no LinkedIn plugin

In Semantic Kernel, a plugin is "a group of functions that can be exposed to AI apps and services". Each of those is a kernel function the model can call. Microsoft ships plugins for the web, documents and Microsoft Graph. None of them reads a LinkedIn profile.

On October 12, 2026, we searched GitHub's code for "linkedin" in the semantic-kernel repo and found 4 files. One holds a kernel function, LinkedInSearchUrl, in the Web plugin. Its description reads "Return URL for LinkedIn search query." It builds a LinkedIn search URL from your query, and doesn't open it. The other three are its test and two sample data files. Of the 68 Semantic Kernel packages on NuGet, none has LinkedIn in its name.

Before you start

  • The .NET SDK, version 10 or 8. Semantic Kernel's README lists .NET 10 or later as a requirement, but its NuGet packages run on .NET 8. We ran both files on .NET 10 and .NET 8.
  • A Datacircle API key: Sign up at datacircle.dev with your work email: a $5 credit, that's 2,105 LinkedIn profiles at $2.375 per 1,000. Log in at datacircle.dev/login with your work email. Your API key is on the page once you're in. Put it in DATACIRCLE_API_KEY.
  • An OpenAI API key in OPENAI_API_KEY. Both files pass it to AddOpenAIChatCompletion, from Semantic Kernel's OpenAI connector, so you don't put the key in any file. Semantic Kernel has connectors for other model services. We haven't tried them.

Each way is a console app. Make the MCP app with these commands. They add Semantic Kernel, as its quick start does, and the MCP C# SDK:

dotnet new console -n McpAgent
cd McpAgent
dotnet add package Microsoft.SemanticKernel --version 1.81.0
dotnet add package ModelContextProtocol --version 2.2.0

The MCP way: our server as a plugin

Semantic Kernel's MCP page shows Python only, and says "MCP Documentation is coming soon for .Net." Its .NET sample connects with the MCP C# SDK, and turns each tool into a kernel function with AsKernelFunction(). This file does the same. Our MCP server is at https://api.datacircle.dev/mcp. It gets a LinkedIn profile from its URL, through Up2Data, HarvestAPI or Fetchin. Put this in Program.cs, then run dotnet run:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using ModelContextProtocol.Client;

string api = Environment.GetEnvironmentVariable("DATACIRCLE_API_URL") ?? "https://api.datacircle.dev";
await using McpClient mcp = await McpClient.CreateAsync(new HttpClientTransport(new()
{
    Endpoint = new Uri(api + "/mcp"),
    // Your Datacircle key, as a Bearer token on each request to our MCP server
    AdditionalHeaders = new Dictionary<string, string>
    {
        ["Authorization"] = "Bearer " + Environment.GetEnvironmentVariable("DATACIRCLE_API_KEY"),
    },
}));
IList<McpClientTool> tools = await mcp.ListToolsAsync();

IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
    "gpt-5.4-mini",
    new Uri(Environment.GetEnvironmentVariable("OPENAI_BASE_URL") ?? "https://api.openai.com/v1"),
    Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
// Of our server's tools, keep get_linkedin_profile only, as a kernel function
builder.Plugins.AddFromFunctions("datacircle",
    tools.Where(tool => tool.Name == "get_linkedin_profile").Select(tool => tool.AsKernelFunction()));
Kernel kernel = builder.Build();
kernel.AutoFunctionInvocationFilters.Add(new CallLimit(3));

ChatHistory chat = new("Read the LinkedIn profile at the user's URL with get_linkedin_profile."
    + " If up2data says its limit is reached, call it again with provider fetchin.");
chat.AddUserMessage("What is the current job title on https://www.linkedin.com/in/example-profile?");
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
ChatMessageContent answer = await kernel.GetRequiredService<IChatCompletionService>()
    .GetChatMessageContentAsync(chat, settings, kernel);
Console.WriteLine(answer);

// Each call is a call to our API: at most this many model answers with calls per question
sealed class CallLimit(int rounds) : IAutoFunctionInvocationFilter
{
    public async Task OnAutoFunctionInvocationAsync(
        AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
    {
        if (context.RequestSequenceIndex >= rounds)
        {
            context.Result = new FunctionResult(context.Function, $"Stopped after {rounds} rounds of calls.");
            context.Terminate = true;
            return;
        }
        await next(context);
    }
}

AdditionalHeaders sends your key to our server as a Bearer token, with each request. The Where call keeps only get_linkedin_profile of our server's tools, and AddFromFunctions makes it a plugin named datacircle. Check the tool name's spelling: when we misspelled the name, the model got no function, and the app ran with no error. The first request to the model was 1,858 bytes with one tool, and 3,993 with all our MCP server's tools.

Auto() in the settings lets the model pick the function, and Semantic Kernel runs it, as its function calling docs say: "By default, functions are set to be automatically invoked." The model sees the function as datacircle-get_linkedin_profile, the plugin's name and the tool's, with our tool's description and its two inputs. It doesn't get the tool's output schema, all 8,291 characters of it, or the instructions our server sends when your app connects.

Semantic Kernel gives the model the whole MCP result, text and structured content

Our server sends each answer as text and as structuredContent. The MCP C# SDK hands Semantic Kernel the whole MCP result, and Semantic Kernel gives it to the model as JSON: our answer as text, then the same answer as structured content, each with datacircle_meta, what the call cost and your balance after it:

{"content":[{"type":"text","text":"{\"data\": …}"}],"structuredContent":{"data":{…},"meta":{…},"datacircle_meta":{…}},"isError":false}

Up2Data's example answer is 1,167 characters as our text, and the model read 2,376. Our stand-in server sent a Fetchin answer of 65,229 characters, and the model read all 132,122. Your model's provider bills those characters as input tokens. To send the model four fields instead, use the kernel function below.

At Up2Data's daily limit, the model reads our error and can call again through Fetchin

The tool takes url, and provider: up2data (the default), harvestapi or fetchin. At Up2Data's limit, the server tells your agent to call again through Fetchin or HarvestAPI. HarvestAPI costs more per 1,000 profiles than the other two, so the file's system message names Fetchin. Our server marks each error with isError, and the model reads our text inside the MCP result:

{"content":[{"type":"text","text":"{\"error\": \"daily up2data limit reached for your account …\"}"}],"isError":true}

We scripted our model to call again with fetchin on this error, and it got the profile. We haven't tested a real model on this error. The model read our text for the other errors we tried, and the app kept running.

With a wrong key, your app stops before any model call

McpClient.CreateAsync connects to our server before Program.cs builds the kernel. We tried a wrong key on api.datacircle.dev, and the app stopped there:

Unhandled exception. System.Net.Http.HttpRequestException: Response status code does not indicate success: 401 (Unauthorized). Response body: {"jsonrpc": "2.0", "id": 2, "error": {"code": -32001, "message": "invalid API key or access token"}}
   at ModelContextProtocol.HttpResponseMessageExtensions.EnsureSuccessStatusCodeWithResponseBodyAsync(…)
   …
   at ModelContextProtocol.Client.McpClient.CreateAsync(…)
   at Program.<Main>$(String[] args) in …/McpAgent/Program.cs:line 7

The exception holds our error message. Set the right key in DATACIRCLE_API_KEY. Without the header, the app stops the same way.

The MCP client waits 100 seconds for a tool call

HttpClientTransport makes its own HttpClient, which times out after 100 seconds, .NET's default. Our API stops waiting for a provider after 45 seconds and returns a 503, so we set no timeout in the file. To see the client's own timeout, we made our stand-in server slower than our API. When it answered after 50 seconds, the model got the profile. When it answered after 105 seconds, the model read this error after 100 seconds, and the app kept running:

Error: Exception while invoking function. The invocation of function 'get_linkedin_profile' was canceled.

The function way: one kernel function that calls our API

You send the provider's own request to api.datacircle.dev, with your Datacircle key. That's the only change. The method sends Up2Data's and Fetchin's own requests, with X-Data-Provider naming the provider. Up2Data's request is in our API reference. Make a second console app, with Semantic Kernel alone:

dotnet new console -n ToolAgent
cd ToolAgent
dotnet add package Microsoft.SemanticKernel --version 1.81.0

Put this in its Program.cs, then run dotnet run:

using System.ComponentModel;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Nodes;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

IKernelBuilder builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion(
    "gpt-5.4-mini",
    new Uri(Environment.GetEnvironmentVariable("OPENAI_BASE_URL") ?? "https://api.openai.com/v1"),
    Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
builder.Plugins.AddFromType<LinkedInPlugin>("datacircle");
Kernel kernel = builder.Build();
kernel.AutoFunctionInvocationFilters.Add(new CallLimit(3));

ChatHistory chat = new("Read the LinkedIn profile at the user's URL with get_linkedin_profile.");
chat.AddUserMessage("What is the current job title on https://www.linkedin.com/in/example-profile?");
OpenAIPromptExecutionSettings settings = new() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() };
ChatMessageContent answer = await kernel.GetRequiredService<IChatCompletionService>()
    .GetChatMessageContentAsync(chat, settings, kernel);
Console.WriteLine(answer);

public sealed class LinkedInPlugin
{
    static readonly string Api = Environment.GetEnvironmentVariable("DATACIRCLE_API_URL") ?? "https://api.datacircle.dev";
    static readonly string? Key = Environment.GetEnvironmentVariable("DATACIRCLE_API_KEY");
    static readonly HttpClient Http = new();

    [KernelFunction("get_linkedin_profile")]
    [Description("Get the current job title, company, location and headline on a LinkedIn profile, from the profile's URL."
        + " Returns job_title, company, location and headline, or error when the profile can't be read."
        + " Each call asks the provider live and may be billed to the Datacircle balance.")]
    public async Task<Dictionary<string, string?>> GetLinkedInProfileAsync(
        [Description("The profile's LinkedIn URL, like https://www.linkedin.com/in/example-profile")] string url)
    {
        HttpResponseMessage answer = await CallAsync("up2data", () =>
            new(HttpMethod.Post, Api + "/v1/profiles/enrich") { Content = JsonContent.Create(new { url }) });
        if ((int)answer.StatusCode == 200)
        {
            JsonNode? profile = JsonNode.Parse(await answer.Content.ReadAsStringAsync())?["data"];
            return new()
            {
                ["job_title"] = profile?["current_company"]?["title"]?.ToString(),
                ["company"] = profile?["current_company"]?["name"]?.ToString(),
                ["location"] = profile?["location"]?["raw"]?.ToString(),
                ["headline"] = profile?["headline"]?.ToString(),
            };
        }
        if ((int)answer.StatusCode == 429) // Up2Data's daily limit: Fetchin answers instead
        {
            answer = await CallAsync("fetchin", () =>
                new(HttpMethod.Get, Api + "/api/v1/profile?profileUrlOrUrn=" + Uri.EscapeDataString(url)));
            if ((int)answer.StatusCode == 200)
            {
                JsonNode? profile = JsonNode.Parse(await answer.Content.ReadAsStringAsync());
                return new()
                {
                    ["job_title"] = profile?["jobTitle"]?.ToString(),
                    ["company"] = profile?["companyName"]?.ToString(),
                    ["location"] = profile?["location"]?.ToString(),
                    ["headline"] = profile?["title"]?.ToString(),
                };
            }
        }
        int status = (int)answer.StatusCode;
        string body = await answer.Content.ReadAsStringAsync();
        return status switch
        {
            404 or 422 => new() { ["error"] = "This LinkedIn profile is private or deleted." },
            400 => new() { ["error"] = "This is not a LinkedIn profile URL."
                + " Send one like https://www.linkedin.com/in/example-profile" },
            402 => new() { ["error"] = "The Datacircle balance is too low for this call."
                + " Tell the user to add funds on their Datacircle dashboard." },
            429 or 500 or 502 or 503 or 504 => new() { ["error"] = $"The provider didn't answer ({status}),"
                + " and the call wasn't charged. Try again in a minute." },
            // 401: DATACIRCLE_API_KEY is wrong
            _ => throw new InvalidOperationException($"Datacircle answered {status}: {body}"),
        };
    }

    // One call through one provider. Fetchin's 429 is its rate limit: wait 1 s and send it again, then 2 s
    static async Task<HttpResponseMessage> CallAsync(string provider, Func<HttpRequestMessage> request)
    {
        for (int wait = 0; ; wait++)
        {
            await Task.Delay(TimeSpan.FromSeconds(wait));
            HttpRequestMessage sent = request();
            sent.Headers.Authorization = new AuthenticationHeaderValue("Token", Key);
            sent.Headers.Add("X-Data-Provider", provider);
            HttpResponseMessage answer = await Http.SendAsync(sent);
            if (provider != "fetchin" || (int)answer.StatusCode != 429 || wait == 2)
            {
                return answer;
            }
        }
    }
}

// Each call is a call to our API: at most this many model answers with calls per question
sealed class CallLimit(int rounds) : IAutoFunctionInvocationFilter
{
    public async Task OnAutoFunctionInvocationAsync(
        AutoFunctionInvocationContext context, Func<AutoFunctionInvocationContext, Task> next)
    {
        if (context.RequestSequenceIndex >= rounds)
        {
            context.Result = new FunctionResult(context.Function, $"Stopped after {rounds} rounds of calls.");
            context.Terminate = true;
            return;
        }
        await next(context);
    }
}

[KernelFunction] makes the method a kernel function, and AddFromType adds the class as a plugin named datacircle. The model gets the function's name, its whole description, and url with its description, but not the return type, so we wrote the answer's keys into the description. Semantic Kernel's plugin docs recommend snake_case for function names and parameters, "even if you're using C# or Java".

The method sends the URL to Up2Data. At Up2Data's daily limit (its 429), it sends the same URL to Fetchin. Fetchin takes 5 requests a second across all our customers, and returns a 429 past that: the method waits one second and sends the request again, then waits two seconds and sends it once more.

The method returns a Dictionary, and Semantic Kernel gives it to the model as JSON:

{"job_title":…,"company":…,"location":…,"headline":…}

For a private or deleted profile, a URL that isn't a profile, a low balance or a provider error, the method returns one key, error, so the model can tell the user. Each field comes from the same JSON path as in our Python post:

Source of each field
FieldUp2Data's answerFetchin's answer
job_titledata.current_company.titlejobTitle
companydata.current_company.namecompanyName
locationdata.location.rawlocation
headlinedata.headlinetitle

With a wrong key (401), the method throws. Semantic Kernel catches the exception and gives its message to the model. We tried a wrong key on api.datacircle.dev, and the model read:

Error: Exception while invoking function. Datacircle answered 401: {"error": "invalid api key"}

The app kept running, so the model can tell you the key is wrong. Set the right key in DATACIRCLE_API_KEY.

The method's HttpClient times out after 100 seconds by default too, longer than the 45 seconds our API waits for a provider. With our stand-in server slower than our API, the model got the profile at 50 seconds, and at 105 seconds it read the same error as in the MCP app, after 100 seconds. In a copy of the file, we set the timeout to 10 seconds, with new() { Timeout = TimeSpan.FromSeconds(10) }, and the model read that error 10 seconds into the call. If you set a timeout, keep it above 45 seconds. DATACIRCLE_API_URL is for tests: point it at a stand-in for our API, and you can run either file without spending your balance.

Cap the calls with a filter

Each function call reaches our API. We bill each call that gets a profile, and each call for a profile Fetchin or HarvestAPI can't find, at the price on our pricing page. The method sends up to four requests per call, and we bill at most one of them: Up2Data's 429 and Fetchin's 429 are free.

Semantic Kernel stops running calls after 128 requests to the model per question. That's the internal constant MaximumAutoInvokeAttempts in Semantic Kernel 1.81.0, with no setting to change it. A scripted model that asked for the function at every turn made 128 calls to a stand-in for our API in under a second. Semantic Kernel didn't run the call in the model's next answer, and returned that answer, with no text: the app printed an empty line.

CallLimit, at the end of both files, is an auto function invocation filter: Semantic Kernel runs it before each call. RequestSequenceIndex counts the model's answers that asked for calls. From the fourth answer, the filter sets Terminate, and Semantic Kernel stops with the filter's text as the answer. Our scripted model made 3 calls, and the app printed:

Stopped after 3 rounds of calls.

The model can ask for more than one call in one answer. Our scripted model asked for two calls in each answer, and made 6 calls under the same cap.

At each turn, Semantic Kernel sends the model the whole conversation so far. In our MCP test, the model called the tool at every turn, and Semantic Kernel added the Fetchin answer of 132,122 characters to the conversation each time, so the requests grew from 1,858 bytes to 145,220, 288,582 and 431,944.

Semantic Kernel runs each call without asking for your approval. A filter like CallLimit is the place to ask: when ours threw an exception, the call didn't reach our server, and the model read this, with the app still running:

Error: Exception while invoking function. not approved

When our filter set a result without calling next, the model read that result instead, as the filter docs say: "Without calling next, the operation will not be executed."

API answers: cost and what the function returns

Our API's answers to the method in ToolAgent's Program.cs
AnswerMeaningCostThe function returns
Up2Data 200the profile$2.375 per 1,000the four fields
Up2Data 422the profile is private or deletedfreeerror: "This LinkedIn profile is private or deleted."
Up2Data 400not a LinkedIn profile URLfreeerror: "This is not a LinkedIn profile URL."
Up2Data 429Up2Data hit its daily limitfreeFetchin's answer
Fetchin 200the profile$1.485 per 1,000the four fields
Fetchin 404 with PROFILE_NOT_FOUNDthe profile is private or deleted$1.485 per 1,000: Fetchin bills the lookuperror: "This LinkedIn profile is private or deleted."
Fetchin 429Fetchin hit its rate limit, which all our customers sharefreeFetchin's answer after up to two retries, or error: "Try again in a minute"
402your balance can't cover the callfreeerror: "The Datacircle balance is too low for this call."
500, 502, 503 or 504the provider failed, or didn't answer within 45 secondsfreeerror: "Try again in a minute"
401your key is wrongfreean exception: the model reads Datacircle answered 401 and our error message

Up2Data takes $1 a day per account (421 profiles), with a shared daily limit for all customers, then answers 429 until 00:00 UTC. HarvestAPI has no daily limit. Fetchin has no daily limit either.

The tests we ran

  • We tested on October 12, 2026, on .NET 10 and .NET 8, with Microsoft.SemanticKernel 1.81.0 (its OpenAI connector 1.81.0) and ModelContextProtocol 2.2.0, the latest on NuGet.
  • We made each project with the commands above, and built each file and variant above with dotnet build. Each test ran the built app, and we also ran each file once with dotnet run. OPENAI_BASE_URL pointed both files at a scripted stand-in for OpenAI's API. Without it, they call OpenAI.
  • We ran the method against a stand-in for our API that returned each answer in the table. We ran the MCP app against a stand-in for our MCP server with the tools our live server lists, and tried each error, a 50 second answer, a 105 second one, the long answer and a wrong tool name.
  • We didn't try a real Datacircle key, a real model or an OAuth sign in.

Cost per 1,000 profiles

We charge your balance the prices on our pricing page, with no markup:

Our price per 1,000 profiles and daily limit, by provider
Up2DataHarvestAPIFetchin
Per 1,000 found$2.375$3.70$1.485
Per 1,000 not foundfree$2.30$1.485
Daily limit421 profiles per accountnonenone

Say your app looks up 1,000 profiles in a day, one call each, and the providers find every one. You pay us at most $1.86: $1.00 for 421 through Up2Data and $0.86 for the other 579 through Fetchin. Through Fetchin alone, the same 1,000 would cost less: $1.485. Calling Up2Data first costs less when the providers miss more than about 37% of the profiles. Both files call Up2Data first because it bills nothing for a profile it can't find. The model may call the function more than once per question. Your model's provider bills you for its tokens.

The same tool in Microsoft Agent Framework, in Python: LinkedIn profiles in Microsoft Agent Framework. In Java: LinkedIn profiles in LangChain4j and LinkedIn profiles in Spring AI. In agent frameworks for Python and TypeScript: LinkedIn profiles in LangChain and LinkedIn profiles in the Vercel AI SDK. The same call from a script: with Python or with Node.js. In Claude, ChatGPT or Cursor: our LinkedIn MCP server. Other vendors' prices per 1,000: LinkedIn profile API pricing compared.

Questions

Does Semantic Kernel have a LinkedIn plugin?

No. Its Web plugin's LinkedInSearchUrl returns a LinkedIn search URL and reads no profile. To read a profile, connect our MCP server at https://api.datacircle.dev/mcp with the MCP C# SDK and AsKernelFunction(). Or write one [KernelFunction] method that sends POST {"url": "<the profile's LinkedIn URL>"} to https://api.datacircle.dev/v1/profiles/enrich, with the headers Authorization: Token <your key> and X-Data-Provider: up2data.

How do I add a remote MCP server to Semantic Kernel in C#?

Connect with McpClient.CreateAsync and an HttpClientTransport, from the ModelContextProtocol package. List the server's tools with ListToolsAsync, and add the ones you need to the kernel as a plugin: builder.Plugins.AddFromFunctions("datacircle", tools.Where(tool => tool.Name == "get_linkedin_profile").Select(tool => tool.AsKernelFunction())). Put an API key in the transport's AdditionalHeaders, as Authorization: Bearer <your key>.

How many function calls does Semantic Kernel allow?

No fixed number of calls: one answer from the model can ask for more than one. With automatic function calling, Semantic Kernel 1.81.0 stops running the calls the model asks for after 128 requests to the model per question. That's an internal constant, with no setting to change it. To stop sooner, add an IAutoFunctionInvocationFilter that sets context.Terminate.

What does the model read when a kernel function throws?

With automatic function calling, Semantic Kernel catches the exception and gives the model "Error: Exception while invoking function." and the exception's message. The app keeps running.

What is the timeout of an MCP tool call in Semantic Kernel?

Semantic Kernel sets none. HttpClientTransport, from the MCP C# SDK, makes its own HttpClient, which times out after 100 seconds, .NET's default. In our test, when the server took over 100 seconds to answer, the model got "Error: Exception while invoking function. The invocation of function 'get_linkedin_profile' was canceled."

How much does a LinkedIn profile cost?

$2.375 per 1,000 through Up2Data (a profile it can't find is free), $3.70 per 1,000 through HarvestAPI. $1.485 per 1,000 through Fetchin, a profile it can't find billed the same. HarvestAPI bills a profile it can't find at $2.30 per 1,000.

Is there a daily limit?

Up2Data takes $1 a day per account (421 profiles), with a shared daily limit for all customers, then answers 429 until 00:00 UTC. HarvestAPI has no daily limit. Fetchin has no daily limit either.

Is each request live, or cached?

Live. Each request goes to the provider and gets the profile as it is today.

Do I need a LinkedIn account?

No. You send the profile's URL with your Datacircle key: no LinkedIn login, no cookies, no browser.

What happens when my balance runs out?

A call your balance can't cover answers 402. Add funds, from $5, on your dashboard.

Get started

Sign up at datacircle.dev with your work email: a $5 credit, that's 2,105 LinkedIn profiles at $2.375 per 1,000.

Sign up
Ask AI about Datacircle

Each opens with our question