Here are answers to common questions and topics related to the Payroll API.
If you’re familiar with our QuickBooks Online Accounting API (referred to as Accounting API for the remainder of this section), you may be used to the REST framework. In this section, we’ll go over some general differences between REST and GraphQL, and some differences between the Accounting API and the GraphQL API.
In REST, you would do a GET
request to retrieve data from the server and a POST
, PATCH PUT
or DELETE
to modify data on the server.
In GraphQL, all requests are ``POST``s and the body of the request tells the server what needs to be done. All of the HTTP calls are made the same way and to the same resource but with different request bodies. To retrieve data, you would use a query operation, and to modify data you would use a mutation operation. More on those operations here.
Let’s say you wanted to read an invoice and find out how that invoice was paid. In REST, these would typically be separate endpoints or entities called Invoice and Payment, which is what the Accounting API does. You would have to make one call to the Invoice endpoint to get the invoice information. You would then find the related Payment ID and then make another call to Payment endpoint to find out more information about the payment transaction.
If you were to do this in GraphQL, you would be dealing with a single resource. Depending on the schema, you would make a call to one resource and ask for the Invoice fields you need and the fields on the related Payment.
The Accounting API would return all the information it has about an entity that is queried. A simple read or query operation would return all the fields associated with the entity for the record that has been requested.
The GraphQL API gives you more control over what data you want from the server. Whether you are doing a query or a mutation, you can specify exactly which fields (and nested fields) you want from the server.
In the Accounting API, when you query for records, each record comes with an ID, which is unique only within that entity. So you may have an Invoice with ID 123 and a Payment with ID 123.
The GraphQL API has global IDs. Each entity returned in response has an ID that is unique across all entities.
The Accounting API returns HTTP status codes like 4** or 5** to indicate different types of errors.
The GraphQL API handles errors differently. Errors related to the API gateway later will return status codes 4** or 5**, but API errors will return HTTP status code 200 and provide all of the necessary information within the response body.
Let’s take a deeper dive into some common operations like create, read, update, delete, and query. We’ll take the example of the Accounting API’s Invoice entity for this section. An Invoice represents a sales form where the customer pays for a product or service later. It is associated with Items, Customers, and Payments. Although Invoice is not supported in the GraphQL API, we’ll do a comparison between how we work with this entity in the Accounting API vs how we would hypothetically do this in the GraphQL API.
In all of the snippets below you will see that in the Accounting API (REST), the response will be the object with all of the fields. In the GraphQL API, however, you can specify which fields you want and only those fields are a part of the response.
Note that these are illustrative examples only.
Request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | POST <baseUrl>/v3/company/<realmID>/invoice { "Line": [ { "DetailType": "SalesItemLineDetail", "Amount": 100.0, "SalesItemLineDetail": { "ItemRef": { "name": "Services", "value": "1" } } } ], "CustomerRef": { "value": "1" } } |
Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | { "Invoice": { "Id": "238", "DocNumber": "1069", "Balance": 100.0, "TxnDate": "2020-07-24", "TotalAmt": 100.0, "CustomerRef": { "name": "Amy's Bird Sanctuary", "value": "1" }, "ShipAddr": { "City": "Bayshore", "Line1": "4581 Finch St.", "PostalCode": "94326", "CountrySubDivisionCode": "CA", "Id": "109" }, "DueDate": "2020-08-23", "Line": [ { "Amount": 100.0, "SalesItemLineDetail": { "TaxCodeRef": { "value": "NON" }, "ItemRef": { "name": "Services", "value": "1" } }, "DetailType": "SalesItemLineDetail" } ], ... other fields ... } |
Request
1 2 3 4 5 6 7 8 | POST <graphBaseURL>/v1 mutation Create{ createInvoice(item: {type: "inventory", amount: 100, itemref: "111"}, customerRef: "222", paymentRef: "333") { id docNumber } } |
Response
1 2 3 4 | { "id": 1, "docNumber": "101" } |
Request Example 1
1 | GET /v3/company/<realmID>/invoice/101 |
Response Example 1
1 2 3 4 5 6 7 8 9 10 | { "Invoice": { "Id": "101", "DocNumber": "1069", "LinkedTxn": { "TxnType": "Payment", "TxnId": "801" } ... other fields ... } |
Request Example 2
1 | GET /v3/company/<realmID>/payment/801 |
Response Example 2
1 2 3 4 5 6 | { "Payment": { "Id": "801", "PaymentType": "CreditCard", ... other fields ... } |
Request
1 2 3 4 5 6 7 8 9 | POST <graphBaseURL>/v1 query getPaymentInfo { invoices (id : 1) { docNumber payments { paymentType } } |
Response
1 2 3 4 5 6 | { docNumber: "101", payments{ paymentType: "Credit Card" } } |
Request
1 2 3 4 5 6 7 8 | POST <baseUrl>/v3/company/<realmID>/invoice { "SyncToken": "0", "Id": "101", "sparse": true, "DueDate": "2020-09-30" } |
Response
1 2 3 4 5 6 7 8 9 10 11 12 13 | { "Invoice": { "Id": "101", "SyncToken": "1", "DocNumber": "1069", "DueDate": "2020-09-30", "Line": [ { ... other fields ... } ], ... other fields ... } |
Request
1 2 3 4 5 6 7 8 | POST <graphBaseURL>/v1 mutation Update($id: ID!) { updateInvoice(id: $id, dueDate: "2020-09-30") { id dueDate } } |
Response
1 2 3 4 | { id: 1, dueDate: "2020-09-30" } |
Request
1 2 3 4 5 6 | POST <baseUrl>/v3/company/<realmID>/invoice?operation=delete { "Id": "239", "SyncToken":"3" } |
Response
1 2 3 4 5 6 7 | { "Invoice": { "domain": "QBO", "status": "Deleted", "Id": "239" } } |
Request
1 2 3 4 5 6 7 | POST <graphBaseURL>/v1 mutation Delete($id: ID!) { deleteInvoice(id: $id) { status } } |
Response
1 2 3 | { status: "void" } |
A common operation is to query invoices based on certain filters. Let’s say we want to get all invoices created within a date range.
Request
1 | GET <baseUrl>/v3/company/<realmID>/query?query=select * from Invoice where TxnDate>'2020-01-18' and TxnDate<'2020-01-18' |
Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "QueryResponse": { "Invoice": [ { "Id": "201", ... other fields ... }, { "Id": "202", ... other fields ... }, ... more records ... ] } } |
Request
1 2 3 4 5 6 7 | query getInvoices() { invoice(filter: {txnDate: {between: ['2020-01-18' , '2020-01-18' ]}}) { id status balance } } |
Response
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | { "data": { "invoice": [ { "id": "101", "status": "Paid", "balance": 0 }, { ... more records ... }, ... more records ... ] } } |
The above are only illustrative examples. To get a full list of operations and resources available in the QuickBooks Payroll API, use the introspection query or our API reference docs. You can also refer to our collections for Insomnia and Postman here.
The following are some commonly asked questions related to Payroll.
What do I need to access payroll data?
As of right now, the scopes necessary to access payroll data are not publicly exposed and cannot be onboarded without Intuit help. To get access you will need to work with an Intuit representative.
Should I be using development keys to test my app?
No, Currently the Payroll service does not have a sandbox/playground/dev environment to work with. As such, as a payroll 3rd-party partner you will be given a test account in the production environment you do not have to pay for, and as such you must use production keys.
Where are the employer contributions stored?
Within the payslip object per employee you will be able to see both the employee and employer contributions. This is set at the employee level, not generally at the company/employeer level to allow for different contribution levels of employees.
Do you expose payroll contribution types?
Not currently. We expose type for other elements within payroll but do not show a need for this information based on what our 3rd party partners are trying to accomplish.
If you encounter an error, the Intuit GraphQL API server returns an error message. These messages give you clues about the issue’s source and cause.
Server responses for gateway and service errors have an intuit_tid
field in the header.
Capture this field’s value. It will help our support team quickly find and address reported issues.
When requests from apps go to our servers, they first pass through a gateway layer. Then they pass through the GraphQL Service layer. Thus, there are two general types: gateway errors and service errors.
Review the HTTP Status Code (xxx). This tells you the error type:
Gateway errors are generated when requests hit our server’s gateway layer. They apply to the whole request and follow the gateway error response format.
1 2 3 4 5 6 7 8 | HTTP Status Code 401 { "code": "AuthenticationFailed", "type": "INPUT", "message": null, "detail": "Malformed bearer token: too short or too long", "moreInfo": null } |
In the server response, review the following fields:
Many gateway errors are self-explanatory. Some require a bit more digging. For 403 errors, you may need to adjust code for user roles. For 401 errors, you may need to update your app’s access or refresh tokens.
SEE ALL GATEWAY ERRORS
Status code | Error details |
---|---|
302 | Validation Error |
401 | Resource redirect or resource has moved |
403 | Unauthenticated access: application authentication failed due to invalid or expired tokens |
403 | Resource not found: routing error, access or configuration on the Gateway, or incorrect resource requested |
405 | Method not allowed: attempt to request other than GET/POST requests |
429 | Too many requests: request is throttled as it exceeded the throttle policy. |
500 | Internal Server Error: missing POST body or other exceptions within application, or a service outage |
502 | Bad Gateway: Infrastructure misconfiguration, propagates response from downstream, or a service outage |
503 | Service unavailable: Outage |
504 | Service timeout: Outage |
Service errors are generated when requests hit our server’s service layer.
Service errors only apply to a subset of requests, not the entire request. Responses follow the standard GraphQL service format.
SEE ALL SERVICE ERRORS
Most service errors are in response to malformed queries, parsing problems, validation issues, or incorrect scopes. As a result, the server won’t authorize the request.
Here’s an example validation error:
1 2 3 4 5 6 7 8 9 10 | "errors": [ { "message": "VAL-0001 Failed to fetch name for account with id#1", "locations": [ { "line": 6, "column": 7 } ], "path": [ "company", "account", 1, "name" ] "extensions": { "classification": "VALIDATION_ERROR" } } ] |
Review the server response and use it as a guide. Each field gives clues for what failed, where, and how to fix it: