Ruby client for the QuickBooks Online API version 3.
- Built on top of the excellent Faraday gem.
- JSON only support.
- Please don't ask about XML support. Intuit has stated that JSON is the primary data format for the QuickBooks API (v3 and beyond). This gem will specialize in JSON only. The
quickbooks-rubygem has fantastic support for those who favor XML.
- Please don't ask about XML support. Intuit has stated that JSON is the primary data format for the QuickBooks API (v3 and beyond). This gem will specialize in JSON only. The
- Features specs built directly against a QuickBooks Online Sandbox via the VCR gem.
- Robust error handling.
- Why qbo_api is a better choice than the quickbooks-ruby gem.
- Part 1: Learn how to spin up the example app.
- Part 2: Running the specs to aid you in understanding a QuickBooks API transaction.
- Part 3: Contributing to the gem.
If you signed up for a Intuit developer account after July 17th, 2017 then you will have to follow OAuth2: Spin up an example
Add this line to your application's Gemfile:
gem 'qbo_api'And then execute:
$ bundle
Or install it yourself as:
$ gem install qbo_api
q = account.qbo_account # or wherever you are storing the OAuth creds
qbo_api = QboApi.new(token: q.token,
token_secret: q.secret,
realm_id: q.companyid,
consumer_key: '*****',
consumer_secret: '********') qbo_api = QboApi.new(access_token: 'REWR342532asdfae!$4asdfa', realm_id: 32095430444)- cd ~/<local dir>
- git clone [email protected]:minimul/qbo_api.git && cd qbo_api
- bundle
- bin/console
- QboApi.production = true
- # OAuth 1
- qboapi = QboApi.new(token: "qyprd2uvCOdRq8xzoSSiiiiii",
token_secret:"g8wcyQEtwxxxxxxm",
realm_id: "12314xxxxxx7",
consumer_key: "qyprdwzcxxxxxxbIWsIMIy9PYI",
consumer_secret: "CyDN4wpxxxxxxxPMv7hDhmh4")
- # OAuth 2
- qboapi = QboApi.new(access_token: "qyprd2uvCOdRq8xzoSSiiiiii", realm_id: "12314xxxxxx7")
- qboapi.get :customer, 1
Intuit will be requiring API client connections to be negotiated over TLS1.2 by December 31st, 2017. Using the default HTTP client (Net::HTTP) with Faraday this is the case with QboApi, however, if you are using another HTTP client you may need to directly set the TLS version negotiation manually.
Some QBO entities have attributes of type DateTime (e.g., Time Activities with StartTime and EndTime). All DateTimes passed to the QBO API must be serialized in ISO 8601 format. If ActiveSupport is loaded, you can achieve proper serialization with the following configuration:
ActiveSupport::JSON::Encoding.use_standard_json_time_format = true
ActiveSupport::JSON::Encoding.time_precision = 0If you're not using ActiveSupport, you'll need to use #iso8601 method to convert your Time/DateTime instances to strings before passing them to a QboApi instance. Failure to do so will result in a raised QboApi::BadRequest exception.
- By default this client runs against a QBO sandbox. To run against the production QBO API URL do:
QboApi.production = true- Logging:
QboApi.log = true- To change logging target from
$stdoute.g.
QboApi.logger = Rails.logger- To run all requests with unique RequestIds.
QboApi.request_id = true- To run individual requests with a RequestId then do something like this:
resp = qbo_api.create(:bill, payload: bill_hash, params: { requestid: qbo_api.uuid })
# Works with .get, .create, .update, .query methods- To run all requests with a Minor version.
QboApi.minor_version = 8- To run individual requests with a minor version then do something like this:
resp = qbo_api.get(:item, 8, params: { minorversion: 8 })
# Works with .get, .create, .update, .query methods invoice = {
"Line": [
{
"Amount": 100.00,
"DetailType": "SalesItemLineDetail",
"SalesItemLineDetail": {
"ItemRef": {
"value": "1",
"name": "Services"
}
}
}
],
"CustomerRef": {
"value": "1"
}
}
response = qbo_api.create(:invoice, payload: invoice)
p response['Id'] # => 65 customer = {
DisplayName: 'Jack Doe',
PrimaryPhone: {
FreeFormNumber: "(415) 444-1234"
}
}
response = qbo_api.update(:customer, id: 60, payload: customer)
p response.fetch('PrimaryPhone').fetch('FreeFormNumber') # => "(415) 444-1234" response = qbo_api.delete(:invoice, id: 145)
p response['status'] # => "Deleted" response = qbo_api.deactivate(:employee, id: 55)
p response['Active'] # => false response = qbo_api.get(:customer, 5)
p response['DisplayName'] # => "Dukes Basketball Camp" response = qbo_api.get(:customer, ["DisplayName", "Dukes Basketball Camp"])
p response['Id'] # => 5 response = qbo_api.get(:customer, ["DisplayName", "LIKE", "Dukes%"])
p response['Id'] # => 5 response = qbo_api.get(:vendor, ["DisplayName", "IN", "(true, false)"])
p response.size # => 28 # Use the .esc() method
name = qbo_api.esc "Amy's Bird Sanctuary"
response = qbo_api.query(%{SELECT * FROM Customer WHERE DisplayName = '#{name}'})
# OR USE .get() method, which will automatically escape
response = qbo_api.get(:customer, ["DisplayName", "Amy's Bird Sanctuary"])
p response['Id'] # => 1 payload = {"AttachableRef":
[
{"EntityRef":
{
"type": "Invoice",
"value": "111"
}
}
],
"FileName": "test.txt",
"ContentType": "text/plain"
}
# `attachment` can be either an IO stream or string path to a local file
response = qbo_api.upload_attachment(payload: payload, attachment: '/tmp/test.txt')
p response['Id'] # => 5000000000000091308Be aware that any errors will not raise a QboApi::Error, but will be returned in the following format:
{"AttachableResponse"=>
[{"Fault"=>
{"Error"=>
[{"Message"=>"Object Not Found",
"Detail"=>
"Object Not Found : Something you're trying to use has been made inactive. Check the fields with accounts, customers, items, vendors or employees.",
"code"=>"610",
"element"=>""}],
"type"=>"ValidationFault"}}],
"time"=>"2018-01-03T13:06:31.406-08:00"} response = qbo_api.cdc(entities: 'estimate', changed_since: '2011-10-10T09:00:00-07:00')
# You can also send in a Time object e.g. changed_since: Time.now
expect(response['CDCResponse'].size).to eq 1
ids = response['CDCResponse'][0]['QueryResponse'][0]['Estimate'].collect{ |e| e['Id'] }
p ids payload = {
"BatchItemRequest":
[
{
"bId": "bid1",
"operation": "create",
"Vendor": {
"DisplayName": "Smith Family Store"
}
}, {
"bId": "bid2",
"operation": "delete",
"Invoice": {
"Id": "129",
"SyncToken": "0"
}
}
]
}
response = api.batch(payload)
expect(response['BatchItemResponse'].size).to eq 2
expect(batch_response.detect{ |b| b["bId"] == "bid1" }["Vendor"]["DisplayName"]).to eq "Smith Family Store" params = { start_date: '2015-01-01', end_date: '2015-07-31', customer: 1, summarize_column_by: 'Customers' }
response = api.reports(name: 'ProfitAndLoss', params: params)
p response["Header"]["ReportName"]) #=> 'ProfitAndLoss'See docs
response = qbo_api.reconnect
#=> if response['ErrorCode'] == 0
#=> p response['OAuthToken'] #=> rewq23423424afadsdfs==
#=> p response['OAuthTokenSecret'] #=> ertwwetu12345312005343453yy=FgSee docs
response = qbo_api.disconnect
#=> if response['ErrorCode'] == 0
#=> # Successful disconnect customer = { DisplayName: 'Weiskopf Consulting' }
begin
response = qbo_api.create(:customer, payload: customer)
rescue QboApi::BadRequest => e
if e.message =~ /Another customer already exists with this name/
# Query for Id using DisplayName
# Do an qbo_api.update instead
end
endNote: There is some overlap with the all and the get methods. The get method is limited to 1000 results where the all method will return all the results no matter the number.
# retrieves all active customers
qbo_api.all(:customers).each do |c|
p "#{c['Id']} #{c['DisplayName']}"
end
# retrieves all active or inactive employees
qbo_api.all(:employees, inactive: true).each do |e|
p "#{e['Id']} #{e['DisplayName']}"
end
# retrieves all vendors by groups of 5
qbo_api.all(:vendor, max: 5).each do |v|
p v['DisplayName']
end
# retrieves all customers by groups of 2 using a custom select query
where = "WHERE Id IN ('5', '6', '7', '8', '9', '10')"
qbo_api.all(:customer, max: 2, select: "SELECT * FROM Customer #{where}").each do |c|
p c['DisplayName']
end p qbo_api.is_transaction_entity?(:invoice) # => true
# Plural is supported as well
p qbo_api.is_transaction_entity?(:invoices) # => true
p qbo_api.is_transaction_entity?(:customer) # => false
p qbo_api.is_name_list_entity?(:vendors) # => true- Check out this article on spinning up the OAuth2 example.
git clone git://github.com/minimul/qbo_api && cd qbo_apibundle- Create a
.envfilecp .env.example_app.oauth2 .env- If needed create an account at https://developer.intuit.com
- Click
Get started coding - Create an app with both the
Accounting&Paymentsselected. - Go to the
Developmenttab and copy the 'Client ID' and the 'Client 'Secret' to your .env as QBO_API_CLIENT_ID and QBO_API_CLIENT_SECRET respectively
- Add a Redirect URI:
http://localhost:9393/oauth2-redirect(or whatever PORT= is in your .env) - Create a new Company (from the manage sandboxes page) Don't use it for anything else besides testing this app. -. Copy the 'Company ID' to your .env as QBO_API_COMPANY_ID
- Start up the example OAuth2 app
ruby example/oauth2.rb
- Go to
http://localhost:9393/oauth2 - Use the
Connect to QuickBooksbutton to connect to your QuickBooks sandbox, which you receive when signing up at https://developer.intuit.com. - After successfully connecting to your sandbox go to :
http://localhost:9393/oauth2/customer/5- You should see "Dukes Basketball Camp" displayed
- Checkout
example/oauth2.rbto see what is going on under the hood.
- Check out this tutorial and screencast on spinning up an example.
git clone git://github.com/minimul/qbo_api && cd qbo_apibundle- Create a
.envfilecp .env.example_app.oauth1 .env- If needed create an account at https://developer.intuit.com
- Click
Get started coding - Create an app with both the
Accounting&Paymentsselected. - Go to the
Developmenttab and copy and paste the consumer key and secret into the.envfile as QBO_API_CONSUMER_KEY and QBO_API_CONSUMER_SECRET respectively.
- Start up the example app
ruby example/oauth.rb
- Go to
http://localhost:9393 - Use the
Connect to QuickBooksbutton to connect to your QuickBooks sandbox, which you receive when signing up at https://developer.intuit.com. - After successfully connecting to your sandbox run:
http://localhost:9393/customer/5- You should see "Dukes Basketball Camp" displayed
- Checkout
example/oauth.rbto see what is going on under the hood.
- Check out this tutorial and screencast on handling a webhook request. Also checkout
example/app.rbfor the request handling code.
See https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html for how to install ngrok and what it is.
-
With either the oauth1 or oauth2 examples running, run:
ngrok http 9393 -subdomain=somereasonablyuniquenamehere -
Go to the
Developmenttab -
Add a webhook, Select all triggers and enter the https url from the ngrok output
https://somereasonablyuniquenamehere/webhooks -
After saving the webhook, click 'show token'. Add the token to your .env as QBO_API_VERIFIER_TOKEN
-
In another tab, create a customer via the (OAuth2) API:
bundle exec ruby -rqbo_api -rdotenv -e 'Dotenv.load; p QboApi.new(access_token: ENV.fetch("QBO_API_OAUTH2_ACCESS_TOKEN"), realm_id: ENV.fetch("QBO_API_COMPANY_ID")).create(:customer, payload: { DisplayName: "TestCustomer" })'(You'll also need to have added the QBO_API_COMPANY_ID and QBO_API_OAUTH2_ACCESS_TOKEN to your .env)There could be a delay of up to a minute before the webhook fires.
It'll appear in your logs like:
{"eventNotifications"=>[{"realmId"=>"XXXX", "dataChangeEvent"=>{"entities"=>[{"name"=>"Customer", "id"=>"62", "operation"=>"Create", "lastUpdated"=>"2018-04-08T04:14:39.000Z"}]}}]} Verified: true "POST /webhooks HTTP/1.1" 200 - 0.0013
Bug reports and pull requests are welcome on GitHub at https://github.com/minimul/qbo_api.
- Check out this tutorial and screencast on running the specs
git clone git://github.com/minimul/qbo_api && cd qbo_apibundle- Create a
.envfilecp .env.test .env- Update it if your are making HTTP requests.
bundle exec rspec spec/
- All specs that require interaction with the API must be recorded against your personal QuickBooks sandbox. More coming on how to create or modifying existing specs against your sandbox.
- Check out this tutorial and screencast on contributing to qbo_api.
Protip: Once your .env file is completely filled out you can use the console to play around in your sandbox
bin/console test
>> @qbo_api.get :customer, 1
The gem is available as open source under the terms of the MIT License.