Back

Step by Step Guide to Building a Harvest API Integration in PHP

Aug 15, 20247 minute read

Introduction

Hey there, fellow code wrangler! Ready to dive into the world of time tracking with Harvest? In this guide, we'll walk through building a robust Harvest API integration using PHP. Harvest's API is a powerhouse for managing time entries, projects, and more. Let's get our hands dirty and build something awesome!

Prerequisites

Before we jump in, make sure you've got:

  • A PHP environment (7.4+ recommended)
  • Composer installed
  • A Harvest account with API credentials

Got all that? Great! Let's roll.

Setting up the project

First things first, let's set up our project:

mkdir harvest-integration && cd harvest-integration composer init composer require guzzlehttp/guzzle

We're using Guzzle to make our HTTP requests nice and easy.

Authentication

Harvest uses OAuth 2.0 for authentication. Here's a quick way to get your access token:

<?php require 'vendor/autoload.php'; $client = new GuzzleHttp\Client(); $response = $client->post('https://id.getharvest.com/oauth2/token', [ 'form_params' => [ 'grant_type' => 'client_credentials', 'client_id' => 'YOUR_CLIENT_ID', 'client_secret' => 'YOUR_CLIENT_SECRET', ] ]); $token = json_decode($response->getBody())->access_token;

Keep that token safe; we'll need it for all our requests!

Making API requests

Let's make a simple GET request to fetch your user info:

$response = $client->get('https://api.harvestapp.com/v2/users/me', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Harvest-Account-Id' => 'YOUR_ACCOUNT_ID', ] ]); $user = json_decode($response->getBody()); echo "Hello, " . $user->first_name . "!";

CRUD operations

Now, let's get our hands dirty with some CRUD operations:

Creating a time entry

$response = $client->post('https://api.harvestapp.com/v2/time_entries', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Harvest-Account-Id' => 'YOUR_ACCOUNT_ID', ], 'json' => [ 'project_id' => 12345, 'task_id' => 6789, 'spent_date' => '2023-05-15', 'hours' => 2.5, ] ]);

Retrieving time entries

$response = $client->get('https://api.harvestapp.com/v2/time_entries', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Harvest-Account-Id' => 'YOUR_ACCOUNT_ID', ] ]);

Updating a time entry

$response = $client->patch('https://api.harvestapp.com/v2/time_entries/123456', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Harvest-Account-Id' => 'YOUR_ACCOUNT_ID', ], 'json' => [ 'hours' => 3.0, ] ]);

Deleting a time entry

$response = $client->delete('https://api.harvestapp.com/v2/time_entries/123456', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Harvest-Account-Id' => 'YOUR_ACCOUNT_ID', ] ]);

Error handling and rate limiting

Always check for errors and respect those rate limits:

try { $response = $client->get('https://api.harvestapp.com/v2/time_entries', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Harvest-Account-Id' => 'YOUR_ACCOUNT_ID', ] ]); } catch (GuzzleHttp\Exception\ClientException $e) { $errorResponse = $e->getResponse(); $errorBody = json_decode($errorResponse->getBody()); echo "Error: " . $errorBody->message; } // Check rate limits $rateLimit = $response->getHeader('X-Rate-Limit-Remaining')[0]; if ($rateLimit < 10) { // Maybe slow down or pause requests }

Advanced features

Pagination

Harvest uses cursor-based pagination. Here's how to handle it:

$nextPage = null; do { $response = $client->get('https://api.harvestapp.com/v2/time_entries' . ($nextPage ? "?page=$nextPage" : ''), [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Harvest-Account-Id' => 'YOUR_ACCOUNT_ID', ] ]); $data = json_decode($response->getBody()); foreach ($data->time_entries as $entry) { // Process each entry } $nextPage = $data->next_page; } while ($nextPage);

Filtering and sorting

Harvest's API supports various filters and sorting options. Here's an example:

$response = $client->get('https://api.harvestapp.com/v2/time_entries', [ 'headers' => [ 'Authorization' => 'Bearer ' . $token, 'Harvest-Account-Id' => 'YOUR_ACCOUNT_ID', ], 'query' => [ 'from' => '2023-05-01', 'to' => '2023-05-31', 'project_id' => 12345, 'sort' => 'spent_date:desc', ] ]);

Testing the integration

Don't forget to test your integration! Here's a simple PHPUnit test example:

use PHPUnit\Framework\TestCase; class HarvestIntegrationTest extends TestCase { public function testCreateTimeEntry() { // Your test code here } }

Best practices and optimization

  • Cache frequently accessed data to reduce API calls
  • Use batch operations when possible
  • Implement exponential backoff for rate limit handling

Conclusion

And there you have it! You've just built a solid Harvest API integration in PHP. Remember, this is just scratching the surface. Harvest's API has tons more features to explore, so don't be afraid to dive deeper.

Keep coding, stay curious, and may your time tracking always be accurate! 🚀