Skip to main content
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

1

Create Personal Access Token

Go to Harvest → Settings → Developers → Create new personal access token
2

Get Account ID

Your account ID is shown in the token creation screen or in your Harvest URL
3

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'];
}
{
  "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');

Manual Pagination

$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

ResourceMethodsDescription
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:
php artisan test:harvest

Additional Resources