A comprehensive Harvest API integration for managing time entries, projects, clients, tasks, and users.
Features
Time Tracking Manage time entries and timesheets
Project Management Track projects, tasks, and assignments
Client Management Manage clients and contacts
Team Management Access user and team information
Configuration
Add your Harvest credentials to your .env file:
HARVEST_ACCESS_TOKEN=your-access-token
HARVEST_ACCOUNT_ID=your-account-id
HARVEST_BASE_URL=https://api.harvestapp.com/v2
HARVEST_TIMEOUT=60
Getting Your Credentials
Create Personal Access Token
Go to Harvest → Settings → Developers → Create new personal access token
Get Account ID
Your account ID is shown in the token creation screen or in your Harvest URL
Add to Environment
Add both access token and account ID to your .env file
Personal access tokens have full access to your Harvest account. Keep them secure.
Laravel Integration
Service Container Registration
Register Harvest as a singleton in your AppServiceProvider:
use Inly\Core\Connectors\Harvest\ Harvest ;
class AppServiceProvider extends ServiceProvider
{
public function register () : void
{
$this -> app -> singleton ( Harvest :: class , function ( $app ) {
return new Harvest (
accessToken : config ( 'harvest.access_token' ),
accountId : config ( 'harvest.account_id' )
);
});
}
}
The stub file at vendor/inly/core/stubs/laravel/app/Providers/AppServiceProvider.php contains a commented example for Harvest registration.
Time Entries
Track time spent on projects and tasks.
List Time Entries
// Get all time entries
$entries = $harvest -> timeEntries () -> list ();
// Filter by date range
$entries = $harvest -> timeEntries () -> list (
from : '2024-01-01' ,
to : '2024-12-31'
);
// Filter by user
$entries = $harvest -> timeEntries () -> list (
userId : 12345
);
// Filter by project
$entries = $harvest -> timeEntries () -> list (
projectId : 67890
);
foreach ( $entries as $entry ) {
echo $entry [ 'id' ];
echo $entry [ 'spent_date' ];
echo $entry [ 'hours' ];
echo $entry [ 'notes' ];
echo $entry [ 'project' ][ 'name' ];
echo $entry [ 'task' ][ 'name' ];
}
Example Time Entry Response
{
"id" : 123456 ,
"spent_date" : "2024-01-15" ,
"hours" : 8.0 ,
"rounded_hours" : 8.0 ,
"notes" : "Worked on feature implementation" ,
"is_locked" : false ,
"is_running" : false ,
"billable" : true ,
"user" : {
"id" : 1234 ,
"name" : "John Doe"
},
"project" : {
"id" : 5678 ,
"name" : "Website Redesign" ,
"code" : "WEB"
},
"task" : {
"id" : 9012 ,
"name" : "Development"
}
}
Get Single Time Entry
$entry = $harvest -> timeEntries () -> get ( '123456' );
echo $entry [ 'hours' ];
echo $entry [ 'notes' ];
echo $entry [ 'spent_date' ];
Create Time Entry
$entry = $harvest -> timeEntries () -> create ([
'project_id' => 5678 ,
'task_id' => 9012 ,
'spent_date' => '2024-01-15' ,
'hours' => 8.0 ,
'notes' => 'Implemented new feature' ,
'external_reference' => [
'id' => 'task-123' ,
'group_id' => 'project-456' ,
'permalink' => 'https://example.com/tasks/123'
]
]);
Update Time Entry
$harvest -> timeEntries () -> update ( '123456' , [
'hours' => 7.5 ,
'notes' => 'Updated time entry notes'
]);
Delete Time Entry
$harvest -> timeEntries () -> delete ( '123456' );
$paginator = $harvest -> timeEntries () -> paginate ();
foreach ( $paginator as $response ) {
$entries = $response -> json ( 'time_entries' , []);
foreach ( $entries as $entry ) {
echo $entry [ 'spent_date' ] . ': ' . $entry [ 'hours' ] . ' hours' ;
}
}
Projects
Manage projects and project assignments.
List Projects
// Get all active projects
$projects = $harvest -> projects () -> list ();
// Include archived projects
$projects = $harvest -> projects () -> list (
isActive : false
);
// Filter by client
$projects = $harvest -> projects () -> list (
clientId : 12345
);
foreach ( $projects as $project ) {
echo $project [ 'id' ];
echo $project [ 'name' ];
echo $project [ 'code' ];
echo $project [ 'client' ][ 'name' ];
echo $project [ 'starts_on' ];
echo $project [ 'ends_on' ];
echo $project [ 'budget' ];
}
{
"id" : 5678 ,
"name" : "Website Redesign" ,
"code" : "WEB" ,
"is_active" : true ,
"is_billable" : true ,
"is_fixed_fee" : false ,
"bill_by" : "Project" ,
"budget" : 50000.0 ,
"budget_by" : "project" ,
"starts_on" : "2024-01-01" ,
"ends_on" : "2024-12-31" ,
"client" : {
"id" : 1234 ,
"name" : "Acme Corp"
}
}
Get Single Project
$project = $harvest -> projects () -> get ( '5678' );
echo $project [ 'name' ];
echo $project [ 'budget' ];
echo $project [ 'client' ][ 'name' ];
Create Project
$project = $harvest -> projects () -> create ([
'client_id' => 1234 ,
'name' => 'New Project' ,
'code' => 'NEW' ,
'is_billable' => true ,
'bill_by' => 'Project' ,
'budget_by' => 'project' ,
'starts_on' => '2024-01-01' ,
'ends_on' => '2024-12-31'
]);
Update Project
$harvest -> projects () -> update ( '5678' , [
'name' => 'Updated Project Name' ,
'budget' => 60000.0
]);
Clients
Manage client information.
List Clients
// Get all active clients
$clients = $harvest -> clients () -> list ();
// Include inactive clients
$clients = $harvest -> clients () -> list (
isActive : false
);
foreach ( $clients as $client ) {
echo $client [ 'id' ];
echo $client [ 'name' ];
echo $client [ 'currency' ];
echo $client [ 'address' ];
}
Get Single Client
$client = $harvest -> clients () -> get ( '1234' );
echo $client [ 'name' ];
echo $client [ 'address' ];
echo $client [ 'currency' ];
Create Client
$client = $harvest -> clients () -> create ([
'name' => 'New Client Corp' ,
'currency' => 'USD' ,
'address' => '123 Business St, City, State 12345'
]);
Update Client
$harvest -> clients () -> update ( '1234' , [
'name' => 'Updated Client Name' ,
'address' => 'New address'
]);
Tasks
Manage tasks that can be assigned to projects.
List Tasks
// Get all active tasks
$tasks = $harvest -> tasks () -> list ();
// Include archived tasks
$tasks = $harvest -> tasks () -> list (
isActive : false
);
foreach ( $tasks as $task ) {
echo $task [ 'id' ];
echo $task [ 'name' ];
echo $task [ 'billable_by_default' ];
echo $task [ 'default_hourly_rate' ];
}
Get Single Task
$task = $harvest -> tasks () -> get ( '9012' );
echo $task [ 'name' ];
echo $task [ 'billable_by_default' ];
Create Task
$task = $harvest -> tasks () -> create ([
'name' => 'Development' ,
'billable_by_default' => true ,
'default_hourly_rate' => 150.0 ,
'is_active' => true
]);
Users
Access team member information.
List Users
// Get all active users
$users = $harvest -> users () -> list ();
// Include archived users
$users = $harvest -> users () -> list (
isActive : false
);
foreach ( $users as $user ) {
echo $user [ 'id' ];
echo $user [ 'first_name' ];
echo $user [ 'last_name' ];
echo $user [ 'email' ];
echo $user [ 'roles' ];
echo $user [ 'weekly_capacity' ];
}
{
"id" : 1234 ,
"first_name" : "John" ,
"last_name" : "Doe" ,
"email" : "john@example.com" ,
"telephone" : "+1-555-0100" ,
"timezone" : "America/New_York" ,
"weekly_capacity" : 144000 ,
"default_hourly_rate" : 100.0 ,
"cost_rate" : 75.0 ,
"roles" : [ "project_manager" , "developer" ],
"is_active" : true ,
"is_contractor" : false ,
"is_admin" : false ,
"is_project_manager" : true
}
Get Single User
$user = $harvest -> users () -> get ( '1234' );
echo $user [ 'first_name' ] . ' ' . $user [ 'last_name' ];
echo $user [ 'email' ];
print_r ( $user [ 'roles' ]);
Get Current User
$currentUser = $harvest -> users () -> me ();
echo $currentUser [ 'first_name' ];
echo $currentUser [ 'email' ];
Available Resources
Resource Methods Description timeEntries()list(), get(), create(), update(), delete(), paginate()Time entry management projects()list(), get(), create(), update(), paginate()Project management clients()list(), get(), create(), update(), paginate()Client management tasks()list(), get(), create(), paginate()Task management users()list(), get(), me(), paginate()User information
Error Handling
use Saloon\Exceptions\Request\ RequestException ;
try {
$project = $harvest -> projects () -> get ( 'invalid-id' );
} catch ( RequestException $e ) {
$statusCode = $e -> getResponse () -> status ();
$errorMessage = $e -> getResponse () -> json ( 'message' );
if ( $statusCode === 404 ) {
// Project not found
} elseif ( $statusCode === 401 ) {
// Invalid credentials
} elseif ( $statusCode === 422 ) {
// Validation error
$errors = $e -> getResponse () -> json ( 'errors' );
}
}
Common Use Cases
Track Time for User
use Inly\Core\Connectors\Harvest\ Harvest ;
class TrackTimeJob
{
public function __construct (
protected Harvest $harvest
) {}
public function handle ( Task $task , float $hours ) : void
{
$this -> harvest -> timeEntries () -> create ([
'project_id' => $task -> harvest_project_id ,
'task_id' => $task -> harvest_task_id ,
'spent_date' => now () -> format ( 'Y-m-d' ),
'hours' => $hours ,
'notes' => $task -> description ,
'external_reference' => [
'id' => $task -> id ,
'permalink' => route ( 'tasks.show' , $task )
]
]);
}
}
Generate Timesheet Report
public function generateTimesheetReport (
User $user ,
Carbon $startDate ,
Carbon $endDate ,
Harvest $harvest
) : Collection {
$entries = $harvest -> timeEntries () -> list (
userId : $user -> harvest_id ,
from : $startDate -> format ( 'Y-m-d' ),
to : $endDate -> format ( 'Y-m-d' )
);
return $entries -> groupBy ( 'project.name' ) -> map ( function ( $entries ) {
return [
'hours' => $entries -> sum ( 'hours' ),
'billable_hours' => $entries -> where ( 'billable' , true ) -> sum ( 'hours' ),
'entries' => $entries -> count ()
];
});
}
Sync Projects to Local Database
public function syncProjects ( Harvest $harvest ) : void
{
$projects = $harvest -> projects () -> list ();
foreach ( $projects as $project ) {
Project :: updateOrCreate (
[ 'harvest_id' => $project [ 'id' ]],
[
'name' => $project [ 'name' ],
'code' => $project [ 'code' ],
'client_name' => $project [ 'client' ][ 'name' ],
'budget' => $project [ 'budget' ],
'starts_on' => $project [ 'starts_on' ],
'ends_on' => $project [ 'ends_on' ],
'is_active' => $project [ 'is_active' ],
]
);
}
}
Rate Limiting
Harvest enforces rate limits:
Free accounts : 100 requests per 15 seconds per user
Paid accounts : 100 requests per 15 seconds per user
The API returns X-RateLimit-Remaining and X-RateLimit-Reset headers. Monitor these to avoid hitting limits.
Testing
Test your Harvest integration:
Additional Resources