Hey there, fellow Ruby enthusiast! Ready to dive into the world of time tracking with Harvest? In this guide, we'll walk through building a robust Harvest API integration. Harvest's API is a powerhouse for managing time entries, projects, and more. Let's get our hands dirty and build something cool!
Before we jump in, make sure you've got:
httparty
and dotenv
gemsIf you're missing any of these, take a quick detour to get set up.
Let's kick things off:
mkdir harvest_integration cd harvest_integration bundle init
Add these to your Gemfile:
gem 'httparty' gem 'dotenv'
Then run bundle install
. Easy peasy!
Harvest uses OAuth 2.0, but we'll keep it simple with personal access tokens.
Create a .env
file:
HARVEST_ACCESS_TOKEN=your_access_token
HARVEST_ACCOUNT_ID=your_account_id
Now, let's set up our API client:
require 'httparty' require 'dotenv/load' class HarvestClient include HTTParty base_uri 'https://api.harvestapp.com/v2' headers 'Authorization' => "Bearer #{ENV['HARVEST_ACCESS_TOKEN']}", 'Harvest-Account-Id' => ENV['HARVEST_ACCOUNT_ID'] end
Time to make our first request:
response = HarvestClient.get('/users/me') puts response.body
If you see your user info, you're on the right track!
Let's create a time entry:
time_entry = HarvestClient.post('/time_entries', body: { project_id: 12345, task_id: 67890, spent_date: Date.today.to_s, hours: 2 })
Retrieving time entries is a breeze:
entries = HarvestClient.get('/time_entries')
Updating and deleting follow a similar pattern. I'll leave those as a fun exercise for you!
Always expect the unexpected:
begin response = HarvestClient.get('/nonexistent_endpoint') rescue HTTParty::ResponseError => e puts "Oops! #{e.message}" end
Harvest caps us at 100 requests per 15 seconds. Let's play nice:
response = HarvestClient.get('/time_entries') remaining = response.headers['x-rate-limit-remaining'].to_i sleep 15 if remaining < 10
Harvest uses cursor-based pagination. Here's a nifty way to handle it:
def get_all_pages(endpoint) results = [] page = 1 loop do response = HarvestClient.get(endpoint, query: { page: page }) results.concat(response['results']) break unless response['next_page'] page += 1 end results end
Want to get fancy? Try setting up webhooks or diving into reporting. The Harvest API docs are your best friend here.
Don't forget to test! Here's a quick RSpec example:
RSpec.describe HarvestClient do it "fetches the current user" do VCR.use_cassette("current_user") do response = HarvestClient.get('/users/me') expect(response.code).to eq(200) expect(response['id']).to be_a(Integer) end end end
And there you have it! You've just built a solid foundation for a Harvest API integration. Remember, this is just the beginning. There's a whole world of time tracking goodness to explore.
Keep coding, stay curious, and may your commits always be green!