Connectors
/
HarvestAPI
Live · 36 tools

HarvestAPI Integration for AI Agents

HarvestAPI's documentation is thorough. Getting your agent to log time in Harvest and pull live LinkedIn data — reliably, for real users, without burning through credits on misconfigured calls — is the part that takes longer than it should.
HarvestAPI
Live

Time Tracking

LinkedIn Data

Status
Live
Tools
36 pre-built
Auth
API Key
Credential storage
Sandbox support

API Key Auth

Secure key storage

Credit-aware

Time tracking + LinkedIn data

The real problem

Why this is harder than it looks

HarvestAPI is not a single product — it's a bridge to two distinct services under one API key: Harvest time tracking and LinkedIn data scraping. That dual nature is the first thing that trips teams up. The setup seems simple (just an API key), but the operational complexity emerges quickly once you move beyond a single-user prototype.

The auth model is API key rather than OAuth. That sounds simpler until you're building a multi-tenant product. Each user in your system needs their own HarvestAPI key stored securely, isolated from every other user's key, and injected into the right request at the right time. Leaking one user's key to another's request isn't just a bug — it means one customer's Harvest account gets billed for another's activity, and one user's LinkedIn session is used to scrape on another's behalf. Building that isolation correctly — with encrypted storage, per-user key lookup, and clean separation at the infrastructure level — is the same problem OAuth connectors have, just with a different credential shape.

Then there's the credit model. HarvestAPI uses pay-as-you-go credits, and different operations cost different amounts — a full profile scrape costs more than a simplified one, and email enrichment costs extra on top of that. An agent that calls scrape_profile without checking credit balance, or calls bulk_scrape_profiles without knowing it routes through a separate Apify account with its own billing, will silently fail mid-workflow or run up unexpected costs. Your agent needs to be credit-aware before calling any scraping operation.

Finally, the connection management tools — sending connection requests, messaging LinkedIn contacts — require a separate credential entirely: the user's li_at LinkedIn session cookie, not the HarvestAPI key. Managing two different credential types per user, keeping the session cookie fresh, and handling the failure mode when a LinkedIn session expires mid-workflow is a distinct engineering problem from the API key management above.

Scalekit handles API key storage, per-user isolation, and credential injection on every call. Your agent names a tool and passes parameters. The plumbing is invisible.

Capabilities

What your agent can do with HarvestAPI

Once connected, your agent has 36 pre-built tools spanning time tracking and LinkedIn data:

  • Log and query Harvest time entries: create duration-based or timer-based entries against any project and task; list and filter by user, client, date range, or billing status
  • Discover Harvest projects and users: list active projects with budget and client details; look up users and their weekly capacity — both needed before logging time
  • Scrape LinkedIn profiles, companies, and jobs: full profile data including employment history, education, and skills; company overviews with headcount, funding, and specialties; complete job listing details with salary and requirements
  • Search LinkedIn at scale: find people by title, company, location, and industry via Lead Search; search companies, jobs, posts, groups, and service providers with rich filter options
  • Track LinkedIn engagement: retrieve post comments, reactions, and replies; pull profile-level activity including recent comments and reactions made by any user
  • Manage LinkedIn connections and messages: send connection requests with personalized notes, accept pending invitations, and send direct messages — all on behalf of a specific LinkedIn account
Setup context

What we're building

This guide connects a productivity assistant agent to HarvestAPI — helping team members log time against Harvest projects and pull LinkedIn data without leaving your product.

🤖
Example agent
Productivity assistant logging Harvest time entries and enriching contacts with LinkedIn data on behalf of each user
🔐
Auth model
API Key auth — each user provides their own HarvestAPI key. identifier = your user ID
⚙️
Scalekit account
app.scalekit.com — Client ID, Secret, Env URL
🐍
Runtime
Python 3.8+ · Node.js also available
Setup

1 Setup: One SDK, One credential

Install the Scalekit SDK. The only credential your application manages is the Scalekit API key — no HarvestAPI secrets, no user tokens, nothing belonging to your users.

pip install scalekit-sdk-python
npm install @scalekit-sdk/node
import scalekit.client import os from dotenv import load_dotenv load_dotenv() scalekit = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENV_URL"), ) actions = scalekit.actions
import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; const scalekit = new ScalekitClient( process.env.SCALEKIT_ENV_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET ); const actions = scalekit.actions;
Already have credentials?
Connected Accounts

2 Per-User Auth: Creating connected accounts

HarvestAPI uses API key auth — there is no OAuth redirect flow. Each user's HarvestAPI key is registered directly in the Scalekit dashboard under the connection's Connected Accounts tab. Scalekit stores it encrypted and injects it into every request for that user automatically.

In your application code, create a connected account reference for each user. The identifier is any unique string from your system — a UUID, email, whatever you use internally.

response = actions.get_or_create_connected_account( connection_name="harvestapi", identifier="user_ha_456" # your internal user ID ) connected_account = response.connected_account print(f"Status: {connected_account.status}") # Status: ACTIVE once the API key has been saved in the Scalekit dashboard
const response = await actions.getOrCreateConnectedAccount({ connectionName: "harvestapi", identifier: "user_ha_456" // your internal user ID }); const connectedAccount = response.connectedAccount; console.log(`Status: ${connectedAccount.status}`); // Status: ACTIVE once the API key has been saved in the Scalekit dashboard

This call is idempotent — safe to call on every session start. Returns the existing account if one already exists.

Calling HarvestAPI

3 Calling HarvestAPI: What your agent writes

With a connected account active, your agent calls HarvestAPI tools using execute_tool. Name the tool, pass parameters. Scalekit handles key retrieval, request construction, and response parsing.

Log a time entry

Log time against a Harvest project and task. Use list_projects first to resolve project_id and task_id — they differ per account. Duration-based entries take hours; timer-based entries take started_time and ended_time.

result = actions.execute_tool( identifier="user_ha_456", tool_name="log_time_entry", tool_input={ "project_id": 12345678, "task_id": 87654321, "spent_date": "2026-03-31", "hours": 1.5, "notes": "Reviewed Q1 pipeline and updated deal stages" } ) # Returns: { "id": ..., "hours": 1.5, "billable": true, "project": { "name": "Acme Corp" }, ... }
const result = await actions.executeTool({ identifier: "user_ha_456", toolName: "log_time_entry", toolInput: { "project_id": 12345678, "task_id": 87654321, "spent_date": "2026-03-31", "hours": 1.5, "notes": "Reviewed Q1 pipeline and updated deal stages" } }); // Returns: { "id": ..., "hours": 1.5, "billable": true, "project": { "name": "Acme Corp" }, ... }

List time entries with filters

Query time entries across projects, users, and date ranges. Useful for weekly summaries or billing reports. is_running: true returns only active timers.

result = actions.execute_tool( identifier="user_ha_456", tool_name="list_time_entries", tool_input={ "from": "2026-03-01", "to": "2026-03-31", "is_billed": False, "per_page": 100 } ) # Returns paginated list of time entries with project, task, user, and hours details
const result = await actions.executeTool({ identifier: "user_ha_456", toolName: "list_time_entries", toolInput: { "from": "2026-03-01", "to": "2026-03-31", "is_billed": false, "per_page": 100 } }); // Returns paginated list of time entries with project, task, user, and hours details

Scrape a LinkedIn profile

Retrieve full profile data by URL or handle. Set main: true for a faster, lower-credit simplified profile. Only enable find_email when you specifically need it — it costs additional credits per successful match.

result = actions.execute_tool( identifier="user_ha_456", tool_name="scrape_profile", tool_input={ "profile_url": "https://www.linkedin.com/in/jeffweiner08", "main": False, "find_email": False } ) # Returns: employment history, education, skills, location, connections count
const result = await actions.executeTool({ identifier: "user_ha_456", toolName: "scrape_profile", toolInput: { "profile_url": "https://www.linkedin.com/in/jeffweiner08", "main": false, "find_email": false } }); // Returns: employment history, education, skills, location, connections count

Search LinkedIn for people

Find prospects using LinkedIn Lead Search. Returns unmasked results with name, title, and LinkedIn URL. All filters support comma-separated multi-values — pass multiple titles or companies in one call.

result = actions.execute_tool( identifier="user_ha_456", tool_name="search_people", tool_input={ "title": "VP of Engineering,Head of Engineering", "company": "Stripe,Airbnb", "location": "San Francisco, CA", "page": 1 } ) # Returns paginated profiles with name, headline, location, and LinkedIn URL
const result = await actions.executeTool({ identifier: "user_ha_456", toolName: "search_people", toolInput: { "title": "VP of Engineering,Head of Engineering", "company": "Stripe,Airbnb", "location": "San Francisco, CA", "page": 1 } }); // Returns paginated profiles with name, headline, location, and LinkedIn URL
Framework wiring

4 Wiring into your agent framework

Scalekit integrates directly with LangChain. The agent decides what to call; Scalekit handles credential injection on every invocation. No key plumbing in your agent logic.

from langchain_anthropic import ChatAnthropic from langchain.agents import AgentExecutor, create_tool_calling_agent from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from scalekit.langchain import get_tools ha_tools = get_tools( connection_name="harvestapi", identifier="user_ha_456" ) prompt = ChatPromptTemplate.from_messages([ ("system", "You are a productivity assistant. Use the available tools to log time in Harvest and look up LinkedIn data."), MessagesPlaceholder("chat_history", optional=True), ("human", "{input}"), MessagesPlaceholder("agent_scratchpad"), ]) agent = create_tool_calling_agent(ChatAnthropic(model="claude-sonnet-4-6"), ha_tools, prompt) result = AgentExecutor(agent=agent, tools=ha_tools).invoke({ "input": "Log 2 hours against the Acme Corp project for today and find the LinkedIn profile of their VP of Engineering" })
import { ChatAnthropic } from "@langchain/anthropic"; import { AgentExecutor, createToolCallingAgent } from "langchain/agents"; import { ChatPromptTemplate, MessagesPlaceholder } from "@langchain/core/prompts"; import { getTools } from "@scalekit-sdk/langchain"; const haTools = getTools({ connectionName: "harvestapi", identifier: "user_ha_456" }); const prompt = ChatPromptTemplate.fromMessages([ ["system", "You are a productivity assistant. Use the available tools to log time in Harvest and look up LinkedIn data."], new MessagesPlaceholder("chat_history", true), ["human", "{input}"], new MessagesPlaceholder("agent_scratchpad"), ]); const agent = await createToolCallingAgent({ llm: new ChatAnthropic({ model: "claude-sonnet-4-6" }), tools: haTools, prompt }); const result = await AgentExecutor.fromAgentAndTools({ agent, tools: haTools }).invoke({ input: "Log 2 hours against the Acme Corp project for today and find the LinkedIn profile of their VP of Engineering" });
Other frameworks supported
Tool reference

All 36 HarvestAPI tools

Grouped by capability. Your agent calls tools by name — no API wrappers to write.

Harvest: Time Tracking
log_time_entry
Create a time entry in Harvest. Supports duration-based (hours) or timer-based (start/end time) modes. Returns entry ID, billable status, and invoice details
list_time_entries
List time entries with filters by project, user, client, task, date range, billed status, or active timer state. Paginated
list_projects
List all Harvest projects with name, client, budget, billing method, dates, and active status. Use to resolve project_id before logging time
list_users
List all users in the Harvest account with name, email, roles, and weekly capacity. Use to resolve user_id for filtering or cross-user logging
get_user
Retrieve a specific Harvest user by ID, including name, email, roles, capacity, and avatar
get_company
Retrieve the Harvest account's company information — name, plan type, clock format, currency, and capacity settings. Takes no parameters
LinkedIn: Profiles & Companies
scrape_profile
Scrape a LinkedIn profile by URL or handle. Returns employment history, education, skills, and contact metadata. Supports simplified (main=true) and email enrichment modes
scrape_company
Scrape a LinkedIn company page by URL, universal name, or search query. Returns headcount, follower count, locations, specialties, and funding data
bulk_scrape_profiles
Batch scrape up to 50 LinkedIn profiles in one request via Apify. Requires a separate Apify API token. Billed at $4 per 1,000 profiles through Apify — separate from HarvestAPI credits
scrape_job
Retrieve full job listing details by URL or job ID — title, company, description, salary, location, workplace type, and applicant count
LinkedIn: Search
search_people
Search LinkedIn via Lead Search with title, company, location, and industry filters. Returns unmasked results. All filters support comma-separated multi-values
search_profiles
Quick profile lookup by name or keywords with title, company, school, and location filters. Use for simple name/title lookups; use search_people for advanced Lead Search
search_companies
Search companies by keyword with industry, location, and headcount range filters. Returns name, domain, industry, and LinkedIn URL
search_jobs
Search job listings by keyword, location, company, workplace type, employment type, experience level, and salary range
search_profile_services
Find freelancers and service providers by service keyword and location. Supports LinkedIn Geo ID override for precise location filtering
search_groups
Search LinkedIn groups by keyword. Returns group name, member count, and description
search_posts
Search LinkedIn posts by keyword, author profile, company, content type, and recency. Sortable by relevance or date
search_geo_id
Look up LinkedIn geographic IDs by location name. Use returned geoId values as precise location overrides in search_jobs and search_profile_services
LinkedIn: Posts & Engagement
get_linkedin_post
Retrieve full post details including content, author, likes, comments, and shares by URL or activity ID
get_profile_posts
Retrieve recent posts from a person's LinkedIn profile with engagement stats and timestamps
get_company_posts
Retrieve recent posts from a company page with likes, comments, and shares. Filterable by recency: 24h, week, or month
get_group_posts
Retrieve posts from a LinkedIn group with author info and engagement stats
get_post_comments
Retrieve comments on a post with commenter profiles, text, and timestamps. Sortable by relevance or date
get_post_reactions
Retrieve users who reacted to a post with name, title, and reaction type (like, celebrate, support, insightful, funny, love)
get_comment_replies
Retrieve replies to a specific comment with author info and engagement stats
get_comment_reactions
Retrieve users who reacted to a specific comment with name, title, and reaction type
get_profile_comments
Retrieve recent comments made by a profile, including post context and timestamps
get_profile_reactions
Retrieve recent reactions (likes, celebrates, etc.) made by a profile on posts and articles
LinkedIn: Groups
get_linkedin_group
Retrieve group details including name, description, and member count by group URL or ID
LinkedIn: Ads
search_linkedin_ads
Search LinkedIn Ad Library by keyword, advertiser, country, and date range. Supports Ad Library search URL input or individual filter parameters — mutually exclusive
get_linkedin_ad_details
Retrieve details of a specific LinkedIn ad from the Ad Library by URL or ad ID — content, creative, advertiser, and targeting
Connection Management (requires li_at cookie)
send_connection_request
Send a connection request to a LinkedIn profile on behalf of an account. Requires the li_at session cookie. Optional personalized message up to 300 characters
get_sent_connection_requests
Retrieve pending outbound connection requests for an account. Requires li_at cookie
get_received_connection_requests
Retrieve pending inbound connection requests. Returns invitation_id and shared_secret needed to accept. Requires li_at cookie
accept_connection_request
Accept a pending connection request using invitation_id and shared_secret from get_received_connection_requests. Requires li_at cookie
send_linkedin_message
Send a direct message to a 1st-degree LinkedIn connection on behalf of an account. Requires li_at cookie. Cannot message non-connections
Account
get_my_api_user
Retrieve HarvestAPI account info including current credit balance, credits used, plan name, and rate limit. Call before high-volume scraping workflows to verify sufficient credits
get_private_account_pools
List private LinkedIn account pools configured on the HarvestAPI account. Private pools route requests through dedicated accounts for better rate limit isolation in high-volume workflows
Connector notes

HarvestAPI-specific behavior

Resolve project_id and task_id before logging time
Harvest uses numeric IDs for projects and tasks — not names. Call list_projects first to get project_id values, then inspect the project's tasks to get the correct task_id. An agent that hardcodes IDs will silently log against the wrong project across different Harvest accounts.
Connection management tools use li_at, not the API key
send_connection_request, get_sent_connection_requests, get_received_connection_requests, accept_connection_request, and send_linkedin_message require the user's LinkedIn session cookie (li_at), not their HarvestAPI key. li_at expires when the LinkedIn session ends. Handle the failure mode explicitly — a missing or expired cookie returns an auth error, not a data error.
bulk_scrape_profiles requires a separate Apify account
This tool routes through Apify and is billed separately from your HarvestAPI credits at $4 per 1,000 profiles. You must supply an apify_token from console.apify.com/settings/integrations. Failing to provide the token returns an error — the HarvestAPI key alone is not sufficient for this tool.
Check credit balance before high-volume workflows
HarvestAPI uses a pay-as-you-go credit model. Different operations have different costs — full profile scrapes cost more than simplified ones, and email enrichment costs extra per match. Call get_my_api_user before any high-volume scraping sequence to verify your balance. Requests that exceed your credit balance fail mid-workflow with no partial results.
Infrastructure decision

Why not build this yourself

The HarvestAPI setup looks simple — just store an API key. But here's what you're actually signing up for:

PROBLEM 01
Per-user API key isolation in a multi-tenant system — one user's HarvestAPI key must never be injected into another user's request, even under the same account
PROBLEM 02
Managing two credential types per user — a HarvestAPI key for scraping and a li_at session cookie for connection management — with different expiry behavior and failure modes
PROBLEM 03
Credit-aware request handling — bulk operations and email enrichment have separate billing, and exhausted credits fail silently mid-workflow without proper pre-flight checks
PROBLEM 04
Encrypted credential storage for API keys that belong to your customers — storing them in plaintext or a general config system is a security liability, not just an engineering shortcut

That's one connector. Your agent product will eventually need Salesforce, GitHub, Gmail, Slack, and whatever else your customers ask for. Each has its own auth quirks and failure modes.

Scalekit maintains every connector. You maintain none of them.

Ready to ship

Connect your agent to HarvestAPI today

Free to start. API key storage fully handled.
HarvestAPI
Live

Time Tracking

LinkedIn Data

Status
Live
Tools
36 pre-built
Auth
API Key
Credential storage
Sandbox support